【デモあり】GSAPで横スクロールアニメーションを実装する
こんにちは。デザイナーの山田です。
ようやく秋の訪れを感じるようになりました。
年々秋の間隔が短くなっている気がしますが、今年はいつまで秋が続くのでしょうか。
京都の冬は寒いので、しっかり防寒対策をしていきたいと思います。
さて先日、弊社のお客様であるプロバンクホーム様のサイト制作を担当させていただきました。
その中で、横スクロールアニメーションを実装する機会があったのですが、今回はその備忘録を兼ねて、実装方法をまとめていきたいと思います。
GSAPについて・導入方法
実装するにあたり、今回はGSAP(GreenSock Animation Platform)というJavaScriptアニメーションライブラリを使用します。GSAPは、軽量かつパフォーマンス・機能性に優れたライブラリで、簡単にアニメーションを実装できることが特徴です。
このサイトにも、GSAPを使用している箇所がいくつかあったりします。気になる方は是非探してみてください。
インストール
GSAPを使用するにあたり、事前準備を終わらせておきましょう。
ファイルをCDNで読み込むか、npmやyarnでインストールしてくるか、環境などに応じてお好きな方法を選んでください。
CDN
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.3/gsap.min.js"></script>
npm / yarn
npm install gsap
yarn add gsap
// モジュールをimport
import { gsap } from "gsap";
また、今回GSAPのプラグインであるScrollTriggerも使用していきますので、こちらの設定も併せて済ませておいてください。
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.3/ScrollTrigger.min.js"></script>
// モジュールをimport
import { ScrollToPlugin } from "gsap/ScrollToPlugin";
// npm / yarnでインストールした場合、`gsap.registerPlugin()`で指定する必要があります
gsap.registerPlugin(ScrollTrigger);
アニメーションを実装する
準備が整ったところで、いよいよ実装していきましょう。
今回、解説にあたってデモページを作成してみました。
この記事では、こちらの内容をベースに説明を進めていきます。
また、解説では便宜上下記のように記述していきます。
.panel要素 … panel
HTML / CSS
まずは、HTMLとCSSです。
<div class="l-hero">
<div class="l-hero-wrapper" id="wrapper">
<div class="l-hero-panel l-hero-panel-01 panel" id="panel01">
<p class="l-hero-panel__contents">Panel 1/4</p>
</div>
<div class="l-hero-panel l-hero-panel-02 panel" id="panel02">
<p class="l-hero-panel__contents">Panel 2/4</p>
</div>
<div class="l-hero-panel l-hero-panel-03 panel" id="panel03">
<p class="l-hero-panel__contents">Panel 3/4</p>
</div>
<div class="l-hero-panel l-hero-panel-04 panel" id="panel04">
<p class="l-hero-panel__contents">Panel 4/4</p>
</div>
</div>
</div>
.l-hero {
overflow: hidden;
position: relative;
.l-hero-wrapper {
width: 400%;
height: 100vh;
display: flex;
flex-wrap: nowrap;
will-change: auto;
}
.l-hero-panel {
width: 100%;
height: 100%;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: center;
background-size: cover;
background-position: center center;
background-repeat: no-repeat;
&-01 {
background-image: url('https://source.unsplash.com/KU1DYLV3tQE');
}
&-02 {
background-image: url('https://source.unsplash.com/RfH5sOHTOek');
}
&-03 {
background-image: url('https://source.unsplash.com/uOcQUMXaUz8');
}
&-04 {
background-image: url('https://source.unsplash.com/aYLTPUkGZ-8');
}
&::after {
content: '';
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 2;
background-color: rgba(#000, .25);
}
}
.l-hero-panel__contents {
flex: 0 0 100%;
text-align: center;
font-weight: 700;
color: #fff;
font-size: 24px;
position: relative;
text-transform: uppercase;
z-index: 3;
}
@media all and (min-width: 1280px) {
.l-hero-panel__contents {
font-size: 36px;
}
}
}
ポイントは、panelを囲うwrapperの長さは、panelの数に応じて調整する必要があることです。今回は4枚のpanelが入っているため、[width: 400%;]としました。
JavaScript
続いて、JavaScriptです。
const wrapper = document.querySelector('#wrapper');
if(wrapper) {
// gsap.registerPlugin(ScrollTrigger); // npm/yarnの際に必要
const panels = gsap.utils.toArray('.panel');
const wrapperWidth = wrapper.offsetWidth;
/**
* 横スクロール開始
*/
gsap.to( panels, {
xPercent: -100 * (panels.length - 1), // transformX
ease: "none", // easingの設定
scrollTrigger: { // scrollTrigger
trigger: wrapper, // アニメーションの対象となる要素
pin: true, // 要素を固定する
scrub: 1, // スクロールとアニメーションを同期させる。数値で秒数の設定に
snap: { // スナップスクロールにする
snapTo: 1 / ( panels.length - 1 ), // スナップで移動させる位置
duration: {min: .4, max: .6}, // スナップで移動する際の遅延時間
ease: "none" // easing
},
end: () => "+=" + wrapperWidth // アニメーションの終了タイミング
}
})
}
panelを指定するにはdocument.querySelectorAll() でもいいと思うのですが、GSAPには便利な.utils.toArray()関数があるため、そちらを使わせていただきました。
const panels = gsap.utils.toArray('.panel');
GSAP内の記述に触れていきます。
まず、xPercentでは、各panelのtranslateXの値を設定しており、画像が横並びになるように調整しています。
gsap.to( panels, {
xPercent: -100 * (panels.length - 1), // transformX
ease: "none", // easingの設定
scrollTrigger内では、wrapper内をスクロールしている際の処理を記述しています。
scrollTrigger: { // scrollTrigger
trigger: wrapper, // アニメーションの対象となる要素
pin: true, // 要素を固定する
scrub: 1, // スクロールとアニメーションを同期させる。数値で秒数の設定に
snap: { // スナップスクロールにする
snapTo: 1 / ( panels.length - 1 ), // スナップで移動させる位置
duration: {min: .4, max: .6}, // スナップで移動する際の遅延時間
ease: "none" // easing
},
end: () => "+=" + wrapperWidth // アニメーションの終了タイミング
}
pin機能でwrapperを画面内に固定することで、スクロールに応じて横並びとなったpanelたちが流れてくる、といった仕様です。
最後のpanelが画面左端に届いたタイミングで固定は解除したいため、アニメーションの終了タイミング(end)はwrapperの長さとしています。
アンカーリンクを設置する
ここまでの実装でも十分だとは思いますが、各panelへのアンカーリンクがあると色々と便利ですよね。
アンカーリンクを実装するために、新たにScrollToPluginプラグインを用いたいと思います。
<!-- CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.3/ScrollToPlugin.min.js">
import { ScrollToPlugin } from "gsap/ScrollToPlugin";
gsap.registerPlugin(ScrollToPlugin);
実装方法はこちらになります。
※動作が不安定になるため、スナップスクロールはOFFにしています。
<div class="l-nav-anchor">
<div class="l-nav-anchor__wrapper">
<ul class="l-nav-anchor__list">
<li class="l-nav-anchor__list-col anchor">
<a href="#panel01" class="l-nav-anchor__list-item">01</a>
</li>
<li class="l-nav-anchor__list-col anchor">
<a href="#panel02" class="l-nav-anchor__list-item">02</a>
</li>
<li class="l-nav-anchor__list-col anchor">
<a href="#panel03" class="l-nav-anchor__list-item">03</a>
</li>
<li class="l-nav-anchor__list-col anchor">
<a href="#panel04" class="l-nav-anchor__list-item">04</a>
</li>
</ul>
</div>
</div>
const wrapper = document.querySelector(this.wrapper);
if(wrapper) {
// gsap.registerPlugin(ScrollToPlugin, ScrollTrigger); // npm/yarnの際に必要
const anchors = document.querySelectorAll(this.anchor);
let index = '';
anchors.forEach( (anchor) => {
anchor.addEventListener( 'click', (e) => {
e.preventDefault();
index = [].slice.call(anchors).indexOf(anchor); // 何番目のアンカーリンクをクリックしたか取得
const target = document.querySelector(e.currentTarget.querySelector('a').getAttribute('href')); // クリックしたアンカーリンクに紐づくpanelを取得
const scrollbarWidth = window.innerWidth - document.body.clientWidth; // スクロールバーの長さを取得
const wrapperOffset = target.offsetLeft * ( wrapper.clientWidth / ( wrapper.clientWidth - window.innerWidth ) ) + scrollbarWidth * index; // 移動位置を取得
gsap.to(window, {
scrollTo: {
y: wrapperOffset,
autoKill: false
},
duration: 1
});
});
});
}
リンクの対象となるpanelの位置を取得し、その値の分だけscrollToで遷移させている仕組みになっています。
まとめ
実装が複雑そうに見える横スクロールアニメーションですが、GSAPを用いることで比較的簡単に組むことができました。
導入の見極めが難しい横スクロールですが、適切なタイミングで用いることができれば、非常に効果的なアニメーションだと思います。
是非皆さんも実装にチャレンジしてみてください。