JavaScriptでスクロールアクションを実装する

## はじめに
Javascriptを用いてスクロールアクションを実装する

## コーディングルール
- ES6 (EcmaScript2015)以降のモダンな記法を使う
- 関数をモジュール化して使用する
- data 属性を使用してDOM要素を取得する

## コード(pug, scss)
すでにpugファイルとscssファイルは用意されており、JSファイルを作成する。
この2つのファイルはすでに用意されているもののため割愛。

```_scrollAction.pug
.scrollAction
  .scrollAction__inner
    .scrollAction__target.--active
      p.scrollAction__label section 0
      p スクロール連動で残り3つを表示させましょう
    .scrollAction__target(data-selector="scroll-action-target")
      p.scrollAction__label section 1
    .scrollAction__target(data-selector="scroll-action-target")
      p.scrollAction__label section 2
    .scrollAction__target(data-selector="scroll-action-target")
      p.scrollAction__label section 3
```

```_scrollAction.scss
@use 'modules/vars';

.scrollAction {
  display: flex;
  justify-content: center;
  margin-top: 128px;

  &__target {
    width: 400px;
    padding: 64px 0;
    color: vars.$themeWhite;
    text-align: center;
    visibility: hidden;
    background-color: vars.$themeBlack;
    opacity: 0;
    transition: all 0.4s;

    &:not(:first-child) {
      margin-top: 64px;
    }

    &.--active {
      visibility: visible;
      opacity: 1;
    }
  }

  &__label {
    font-family: 'League Gothic', sans-serif;
    font-size: 64px;
    letter-spacing: 0.1em;
  }
}

```

## JSコード解説
全文は最後に記述しています。
まず1つずつ実際のコードの解説をしていきます。

### 変数の定義
以下の変数は、スクロールアクションの機能を実装するための設定や対象要素を指定します。

```javascript
const dataSelector = '[data-selector=scroll-action-target]';
const ACTIVE = '--active';
```

ここでは2つの変数を定義しています。

- `dataSelector`：スクロールアクションの対象となる要素を特定するためのセレクタです。
- `ACTIVE`：要素がビューポートに表示されたときに追加するCSSクラス名です。

### Intersection Observerの設定
`Intersection Observer` は、要素がビューポートに表示されたかどうかを非同期に監視するためのAPIです。

```
const config = {
    root: null,
    threshold: 0.1,
};
```

- `root`：監視対象の要素が交差する基準となる要素を指定します。null の場合はビューポートを基準とします。
- `threshold`：要素がビューポートの何%表示されたときにコールバックを実行するかを指定します。この場合、要素が10%表示されたときにコールバックが実行されます。

### `Intersection Observer`のコールバック関数
以下の関数は、監視対象の要素がビューポートに表示された際に実行されるものです。

```javascript
const observerCallback = (entries, observer) => {
    entries.forEach((entry) => {
        const { target } = entry;

        if (entry.isIntersecting) {
            target.classList.add(ACTIVE);
            observer.unobserve(target);
        }
    });
};
```
この関数内では、監視対象の各要素に対して以下の操作を行います。

- `entry.isIntersecting`：要素がビューポートに表示されているかを判断します。
- `target.classList.add(ACTIVE)`：要素がビューポートに表示されている場合、指定したCSSクラスを追加してスタイルを変更します。
- `observer.unobserve(target)`：一度スタイルを変更した要素は、これ以上監視しないようにします。

### Intersection Observerのインスタンス作成

```javascript
const observer = new IntersectionObserver(observerCallback, config);
```

この行では、新しい `Intersection Observer` のインスタンスを作成しています。
`Intersection Observer` のコンストラクタは2つの引数を受け取ります：

- `observerCallback`：監視対象の要素がビューポートに交差した際に実行されるコールバック関数。
- `config`：監視の動作をカスタマイズするための設定オブジェクト。

これにより、`observer` インスタンスは、指定した設定に基づいて要素の監視を行い、要素がビューポートに交差した際には `observerCallback` 関数を実行するように設定されます。

### 要素の監視の開始
```javascript
targets.forEach((target) => {
    observer.observe(target);
});
```
ここでは、監視対象の要素（targets）のそれぞれに対して、`observer.observe(target)` を実行しています。これにより、指定した要素の監視が開始されます。

`observe` メソッドは、指定した要素を監視対象として追加し、その要素がビューポートに交差するかどうかを非同期に監視します。要素がビューポートに交差した場合、先ほど定義した `observerCallback` 関数が実行されます。

### モジュール化処理を実行し、index.jsでimportする
最終行で下記コマンドを打つことで、モジュール化したJSを外部からimportできるようになり、
それをindex.js でimportしてあげます。

```javascript
export default scrollAction;
```

```javascript
import scrollAction from './modules/scrollAction';

scrollAction();
```

## 最終的なJSコード
```scrollAction.js
/**
 * スクロールアクション機能を実装する関数
 * @function scrollAction
 */
const scrollAction = () => {
  /**
   * 監視対象のセレクタを指定する文字列
   * @type {string}
   */
  const dataSelector = '[data-selector=scroll-action-target]';

  /**
   * 監視対象となる全てのDOM要素を取得
   * @type {NodeList}
   */
  const targets = document.querySelectorAll(dataSelector);

  /**
   * Intersection Observerの設定
   * @type {IntersectionObserverInit}
   */
  const config = {
    root: null,
    threshold: 0.1,
  };

  /**
   * サブメニューの表示状態を制御するCSSクラス名
   * @type {string}
   */
  const ACTIVE = '--active';

  /**
   * Intersection Observerのコールバック関数
   * @param {IntersectionObserverEntry[]} entries
   * @param {IntersectionObserver} observer
   */
  const observerCallback = (entries, observer) => {
    entries.forEach((entry) => {
      const { target } = entry;

      if (entry.isIntersecting) {
        target.classList.add(ACTIVE);
        observer.unobserve(target);
      }
    });
  };

  /**
   * Intersection Observerを作成
   * @type {IntersectionObserver}
   */
  const observer = new IntersectionObserver(observerCallback, config);

  targets.forEach((target) => {
    observer.observe(target);
  });
};

export default scrollAction;
```

以上。