GSAPのScrollTriggerで横スクロール制御

## はじめに
世の中にはリッチで素敵なアニメーションが効いたwebサイトがたくさんあり、思わず見惚れてしまうことがありますよね...!
そんなwebサイト達を眺めていた時、ある地点までスクロールした途端に横スクロールに切り替わるアニメーションがあり、その実装方法が気になったので調べて今回軽いデモを作成してみました。

これがはじめての記事投稿で拙い内容になってしまいましたが、どなたかのお役に立てれば幸いです！

## GSAPの「ScrollTrigger」とは？

**GSAP**（GreenSock Animation Platformの略）はGreenSock社が開発しているJavaScriptのアニメーションライブラリであり、シンプルで豊富なアニメーションを表現できるため、web制作のアニメーション実装を手軽に行うことができます。

その中のライブラリのひとつに「**ScrollTrigger**」があります。
これはその名の通り、ページスクロールをトリガーとして、設定したタイミングでアニメーションを実行することができるライブラリです。

## 導入方法
GSAPの導入方法にはnpm・ダウンロード・GitHubなど何通りかありますが、今回はそのうちの**scriptタグを貼り付ける方法**を選択しました。

2022/12月現在の最新のバージョンで、GSAPとScrollTriggerをそれぞれhtmlファイルのhead内に記述してみます。

```html:HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.3/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.3/ScrollTrigger.min.js"></script>
```
これで、Javascript内でGSAPとScrollTriggerを利用できるようになりました！

## 作ったもの
今回作ったのは以下のgifのシンプルな内容です。
**・アニメーションなしエリアからスクロールしていき、GSAP ScrollTriggerエリアの上端が画面上端と重なった瞬間に横スクロールに切り替わる。**
**・４枚の画像のスクロールが終わると、また普通の縦方向スクロールに戻る。**
といった特徴があります。
※画面右端に赤文字と緑文字が見えますが、ScrollTriggerのマーカー機能を使っています。
![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/648112/efe2c48d-3894-d61a-aecf-6bf2942248a8.gif)

## 実装

```html:HTML
<section class="common__section">
  <div class="empty__container common__container">
    <h2 class="common__title">アニメーションなし<br>エリア</h2>
  </div>
</section>

<section class="side-scroll__section common__section">
  <div class="common__container">
    <div class="side-scroll__itemOuter">
      <div class="side-scroll__itemInner">
        <h2 class="common__title">
          GSAP ScrollTrigger<br>エリア
        </h2>
        <img class="side-scroll__item" src="https://source.unsplash.com/VDXtVYJVj7A" alt="">
        <img class="side-scroll__item" src="https://source.unsplash.com/Av90kkK6mp0" alt="">
        <img class="side-scroll__item" src="https://source.unsplash.com/QKo-op_gR9I" alt="">
        <img class="side-scroll__item" src="https://source.unsplash.com/3CJbeZr9Qjg" alt="">
      </div>
    </div>
  </div>
</section>
```

```css:CSS
/* リセットCSS */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

ul,
li {
  list-style: none;
}

body {
  background-color: #A4BE7B;
  color: #FFF;
  font-family: Arial, Helvetica, sans-serif;
}

/* 共通 */
.common__section {
  padding: 100px 0;
  width: 100%;
}

.common__container {
  display: flex;
  margin: 0 auto;
  max-width: 900px;
  width: 80%;
}

.common__title {
  font-size: 40px;
  font-weight: bold;
  letter-spacing: 0.05em;
}

/* アニメーションなしエリア */
.empty__container {
  height: 900px;
}

/* GSAP ScrollTriggerエリア */
.side-scroll__section {
  background-color: #285430;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;/* はみ出た部分を隠しておく */
}

.side-scroll__itemOuter {
  position: relative;
  height: 700px;
  width: 100%;
  
}

.side-scroll__itemInner {
  position: absolute;
  top: 60px;
  left: 0;
  display: flex;
  gap: 0 60px;
}

.side-scroll__item {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-left: 80px;
  height: 400px;
  width: 600px;
  
}
```

```javascript:JS
const itemWrapper = document.querySelector('.side-scroll__itemOuter');
const itemInner = document.querySelector('.side-scroll__itemInner');

gsap.to(itemInner, {
  x: () => -(itemInner.clientWidth - itemWrapper.clientWidth),
  ease: 'none',
  scrollTrigger: {
    trigger: '.side-scroll__section', 
    start: 'top top',
    end: () => `+=${itemInner.clientWidth - itemWrapper.clientWidth}`,
    markers: true,
    scrub: true, 
    pin: true,  
    invalidateOnRefresh: true, 
    anticipatePin: 1, 
  },
});
```

<br>
<br>
ではJavaScriptについて細かく見ていきましょう。
まずは、

```
gsap.to(第一引数, 第二引数);
```
と記述します。
`第一引数`にはターゲットとなる要素を指定、`第二引数`は{}で囲み、第一引数のターゲットとなる要素にどのようなアニメーションをさせるかを指定します。


```javascript:JS

gsap.to(itemInner, {
  //各種設定を記述
  },
});
```

今回横スクロールさせたいターゲットとなる要素`itemInner`に対し、{}の中でアニメーション指定を行います。
<br>

```javascript
//x方向にどれくらい動かすか (ターゲットとなる要素の横幅 - ターゲットとなる要素のラッパーの横幅)
x: () => -(itemInner.clientWidth - itemWrapper.clientWidth) 
```
画面リサイズに対応するため関数で指定し、左移動(X軸のマイナス方向）なので横幅の値はマイナスにします。

また、`ターゲットとなる要素の横幅`から`ターゲットとなる要素のラッパーの横幅`を引いていますが、これは**ターゲットとなる要素の右端がターゲットとなる要素のラッパーの右端と重なるタイミングまで横スクロールさせたいため**です。
<br>

```javascript
//イージング(加減速)は今回指定なし
ease: 'none',
```


イージング指定の下にscrollTrigger特有の指定を記述しています。

```javascript
scrollTrigger: {
    trigger: '.side-scroll__section', 
    start: 'top top', 
    end: () => `+=${itemInner.clientWidth - itemWrapper.clientWidth}`,
    markers: true,
    scrub: true, 
    pin: true, 
    invalidateOnRefresh: true, 
    anticipatePin: 1, 

  },

```
こちらもそれぞれ設定の詳細を見ていきましょう。
<br>

```javascript
//アニメーション発火のトリガーとなる要素を指定
trigger: '.side-scroll__section'
```
ここではアニメーション発火のトリガーとして、「GSAP ScrollTriggerエリア」全体を指すクラスを指定しています。
<br>
```javascript
//scrollTriggerの開始位置
start: 'top top'
```
1つめの`top`はトリガーとなる要素の部分を指します。
2つめの`top`はビューポートの部分を指します。
つまり、トリガーとなる要素の上端がビューポートの上端にきたら開始という意味になります。
<br>
```javascript
//scrollTriggerの終了位置
end: () => `+=${itemInner.clientWidth - itemWrapper.clientWidth}`
```
ターゲットとなる要素を横スクロールさせた分だけアニメーションさせたいので、`x`と同じ式にしています。
`+=`を使用する時はテキストと見做されるため、テンプレートリテラルで記述しています。
また、`x`と同様、画面リサイズに対応するため関数で指定しています。
<br>


```javascript
//マーカー機能
markers: true
```
デバッグができるように、マーカー機能を`true`にしています。
この設定を行うと、アニメーション開始位置を緑文字・アニメーション終了位置を赤文字で画面上に示してくれるようになります。
<br>

```javascript
//スクロールと同期
scrub: true
```
`true`にすることでスクロールとアニメーションを同期させることができます。
<br>

```javascript
//要素を画面に固定
pin: true
```
`true`にすることでScrollTriggerが発火している間、ターゲットとなる要素を画面に固定(ピン留め)します。
<br>

```javascript
//画面リサイズ時の値再計算
invalidateOnRefresh: true
```
`true`にすることで画面リサイズ時に値の再計算を行ってくれます。今回は`x`と`end`の再計算が対象となります。
<br>

```javascript
//高速スクロール時画面ずれ防止
anticipatePin: 1
```
画面の固定(ピン留め)を行っている場合、高速スクロールしたときにピン留めのタイミングが遅れて見えることがあるため、それを防止するために数値`1`を設定します。通常はこの値で十分ですが、この値を小さくしたり大きくしたりして、ピン留めを行うタイミングを調整することができます。

## まとめ
以上、記述範囲に限定しての内容となってしまいましたが、いかがでしたでしょうか？
個人的にはGSAPであらかじめ用意されているオプションが豊富で、あっという間に実装ができるのでもっと使いこなしていきたいなと思いました。

それでは、ここまで読んでいただきありがとうございました！！

## 参考
- [GSAP公式ページ](https://greensock.com/docs/v3/Plugins/ScrollTrigger)