GSAP ScrollToPluginでページ内リンクの移動をスムージングする

# ScrollToPluginとは

https://greensock.com/docs/v3/Plugins/ScrollToPlugin

ScrollToPluginとは、**GSAPのプラグインの1つ**で、**windowやDOM要素のスクロールにアニメーションを追加する**ものです。
（このプラグインを使用する際にClub GreenSockに加入する必要はありません。）

GSAPのプラグインにしては機能も使い方もかなりシンプルなプラグインで、GSAPのCoreモジュールの基本的な使い方を知っていれば、何も抵抗感なく、また、簡単に扱うことが可能だと思います。

## 使い方

### 基本

ウィンドウを任意の位置までスクロールさせるには、以下の様に`window`をターゲットとして指定し、`scrollTo`にスクロール終了位置を指定します。

```js
// 画面上部から400px下にスクロール
gsap.to(window, { duration: 2, scrollTo: 400 });

// 'someID'というIDを持つ要素までスクロール
gsap.to(window, { duration: 2, scrollTo: '#someID' });
```

ある要素内でスクロールさせるには、下記のようにターゲットに要素を指定します。
また、この指定した要素には`overflow: scroll`を設定する必要があります。

```js
// elemクラスの付与された要素の上から250px下にスクロール
gsap.to('.elem', { duration: 2, scrollTo: 250 });
```

### x方向のスクロール

x方向へのスクロールも以下のように指定することができます。

```js
// 上から400px 左から200px
gsap.to('.elem', { duration: 2, scrollTo: { y: 400, x: 200 }, ease: 'power2' });
```

### 最大値までスクロール

最大値（一番下）までスクロールさせたい場合は、`max`を指定することもできます。

```js
gsap.to('.elem', { duration: 2, scrollTo: { y: 'max' } });
// もしくは
gsap.to('.elem', { duration: 2, scrollTo: 'max' });
```

### オフセット

`offsetX`や`offsetY`を指定することでスクロール先の位置からのオフセットを指定することができます。

```js
// #section1要素の上50pxオフセットをつけてスクロール
gsap.to(window, { duration: 2, scrollTo: { y: '#section1', offsetY: 50 } });
```

### アニメーションのキャンセル

`autoKill`を`true`に設定することで、ScrollToPluginによるアニメーション中にスクロールバーを直接ドラッグしたりしてスクロール位置を変更しようとしたときに、これを検知してアニメーションをキャンセルさせることができます。

```js
gsap.to(window, { duration: 2, scrollTo: { y: 400, autoKill: true }, ease: 'power2' });
```

また、このautoKillがいつ発火したかは`onAutoKill`で検出することができます。

```js
gsap.to(window, { duration: 2, scrollTo: { y: 400, autoKill: true, onAutoKill: autoKillFunction }, ease: 'power2' });
```

# サンプル

<p class="codepen" data-height="600" data-default-tab="result" data-slug-hash="yLvXaoO" data-user="heeroo-ymsw" style="height: 600px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/heeroo-ymsw/pen/yLvXaoO">
  ScrollToPlugin_Tutorial</a> by heeroo-ymsw (<a href="https://codepen.io/heeroo-ymsw">@heeroo-ymsw</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## 軽い解説

ページ内リンクのスクロールにアニメーションをつけてみました。

aタグにそのままScrollToのアニメーションをつけてもそのまま瞬時に遷移してしまうので、デフォルトのクリック処理を`preventDefault()`でキャンセルしています。

```js
navLink.addEventListener("click", (e) => {
  e.preventDefault();
```

また、ヘッダメニューが画面上部に追従してくるので、ScrollToにはヘッダ分のoffsetを追加しています。

```js
gsap.to(window, {
  duration: 1,
  ease: "power3.inOut",
  scrollTo: {
    y: navLink.getAttribute("href"),
    offsetY: 96,
    autoKill: true
  }
});
```

こちらは基本的なJavaScriptの扱い方になりますが、href属性の値を取得する際、念の為`getAttribute()`の前に`hasAttribute()`でhrefに値が入っていることを確認しています。

`getAttribute()`の説明として、MDN Web Docsでは、以下のように記されています。

> 属性が存在しない場合、値は null か "" （空文字列）のどちらかになります。（中略）結果的に、指定された要素に指定された属性が存在しない可能性があるのであれば、 element.hasAttribute() を使用して属性の存在をチェックしてから getAttribute() を呼び出すべきでしょう。[^注釈]

[^注釈]: [Element.getAttribute() - Web API | MDN](https://developer.mozilla.org/ja/docs/Web/API/Element/getAttribute#%E5%B1%9E%E6%80%A7%E3%81%8C%E5%AD%98%E5%9C%A8%E3%81%97%E3%81%AA%E3%81%84%E5%A0%B4%E5%90%88)より抜粋

また、`startsWith()`にnullを渡すとエラーになってしまうため、どっちみち存在チェックをしておく方が良いでしょう。
存在が確認できれば、仮に`<a href>link</a>`という指定だったとしても、`getAttribute("href")`は空文字を返します。つまり、`startsWith()`はエラーを吐かないということです。

```js
if (navLink.hasAttribute("href") && navLink.getAttribute("href").startsWith("#")) {
```

論理積演算子は左から順番に評価し、`false`が現れた時点でそれ以降の式は評価せず、この評価式全体は`false`と判断されます。
つまり、この一文では、「href属性の存在チェックを行い、それが`true`だった時に初めてhref属性値がハッシュから始まるアンカーを指定しているかを確認する」、ということになります。

# まとめ

GSAPのプラグインであるScrollToPluginを使用して、ページ内リンクへのスムーススクロールを行う方法を紹介しました。

jQueryのscrollTop、animateとeasing pluginなどを用いれば同じようなことができますが、Vanilla JSで既にGSAPのCoreモジュールをインポートしている場合や、GSAPの使用感が手に馴染んでいる方などは十分使用を検討する選択肢に入ると思いますので、ぜひ活用してみてください。