グロービス学び放題の字幕機能の秘密大公開！😄

こんにちは、グロービスのフロントエンジニアAgataです。Udemyのような、動画学習サービスの[グロービス学び放題](https://unlimited.globis.co.jp/)というサイトの開発しております。

## 問題点

最近、ラーニングサポートのため、動画の隣に動画の全体の字幕が表示されるツールが増えてます。

例として、ユーチューブで使える[「Language Learning with Netflix & YouTube-AFL」プラグイン](https://chromewebstore.google.com/detail/language-learning-with-ne/bekopgepchoeepdmokgkpkfhegkeohbl?pli=1)みてみましょう。

![YouTubeの字幕プラグイン：紹介](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3640049/6d8c1702-37cd-a15d-cde3-34122f29fc87.gif)
⬆️[「Language Learning with Netflix & YouTube-AFL」](https://chromewebstore.google.com/detail/language-learning-with-ne/bekopgepchoeepdmokgkpkfhegkeohbl?pli=1)プラグインデモ
（[ソース](https://www.youtube.com/watch?v=p7HKvqRI_Bo&ab_channel=TED-Ed): TED-Ed、How does the stock market work? - Oliver Elfenbaum）

動画再生とともに、右がわにある字幕が自動的にスクロールされます。
ツールをもう少し使ってみたら、一つ問題に気づくでしょう：
**動画再生中、ユーザーがマウスなどで字幕をスクロールしても、自動で元の位置に戻ってしまう😅**

![YouTubeの字幕プラグイン：問題](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3640049/ec64ef4a-f42e-1c26-af79-96a5ac48f7dc.gif)
⬆️ 自分でスクロールしても、再生中の字幕に戻ってしまう
（[ソース](https://www.youtube.com/watch?v=p7HKvqRI_Bo&ab_channel=TED-Ed): TED-Ed、How does the stock market work? - Oliver Elfenbaum）

単純かのために、この記事では:
- **「自動スクロール」**: 字幕が自動的にスクロールされること
- **「ユーザースクロール」**：ユーザーが自分の意志で字幕をスクロールすること（マウスやタッチパッドなどで）

と呼びます。

グロービス学び放題では、このような字幕機能の実装に関わって、上記の問題を解決できました。解決までに辿り着く問題と解決仕方をこの記事で説明させていただきます。

### 修正アイデア

上記に説明した問題を解決するために、デザインをもらいました。
仕様は下記のようにでした：
- 現在の状態：
    - 自動スクロール実装済み：動画の再生とともに、字幕が自動的にスクロールされました。
    - 実装方法：動画の時間がアップデートされるたびに、字幕が入っているdivをscrollToでスクロールさせました。
    - 問題：ユーザーが自分でスクロールしても、字幕の自動スクロールが止まってないこと
- 解決仕様：
    - ユーザーが自分で字幕をスクロールすると、自動スクロールが止まって、「Resume Auto Scrolling」ボタンが表示されます ![Resume Auto Scrollingボタン](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3640049/9062062b-7236-857b-f6a7-f32f5a163854.png)

    - ユーザーが「Resume Auto Scrolling」ボタンを押すと、自動スクロールが再生されます。ボタンが消えます。

## 実装アイデア 1 と２

### 実装アイデア1
`やり方：イベントリスナーで「自動スクロール」と「ユーザースクロール」を区別する`
`使えない理由：区別できない 💦`


なぜ「自動スクロール」と「ユーザースクロール」を区別する必要があったかというと、
**「自動スクロール」のスクロールイベントの場合は、自動スクロールを止めちゃいけなからです。**
「ユーザースクロール」のスクロールイベントの場合だけ、自動スクロールを止める必要があります。

スクロールのイベントリスナー：
| スクロール方法 | onScroll | onScroll以外 |
| --- | --- | --- |
| 1. マウス | 🟢 | 🟢（onWheel） |
| 2. タッチパッド | 🟢 | 🟢 (onTouchMove) |
| 3. スクロールバー | 🟢 | ❌ |
| 4. 自動スクロール（scrollTo） | 🟢 | ❌ |

３つ目と４つ目の場合だと、スクロールイベントをonScrollでキャッチしかできないみたいです（間違えていたら、コメントで教えてください！）。
なので、3. スクロールバーのスクロールイベントと、4. 自動スクロールのスクロールイベントを区別できない状態です。

### 実装アイデア2
`やり方：スクロールされた行数で「自動スクロール」と「ユーザースクロール」を区別する`

- 自動スクロール: 必ず１行〜３行のスクロール　（それ以外の場合は、ユーザースクロールとする）![１行](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3640049/b54d6b59-f58e-80d0-9dee-d7449bf8c10b.png)

    
- ユーザースクロール：４行以上にする　（ユーザーが１行、１００行でもスクロール可能です）![４行](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3640049/fdce4821-53b0-e756-23ec-9232f5eb9ab9.png)

この情報を使って、下記のアルゴリズムを書いてみました。

**アルゴリズム１**：

- onScrollイベントリスナーを字幕が入っているdivに追加する。
        ```typescript
        <div onScroll={onScroll}> 字幕 </div>
        ```
- onScrollのイベントリスナーでは、スクロールされた行数を確認する。
    - ４行以上の動きを確認できたら：
        - 自動スクロールをストップさせる
        - 「Resume Auto Scrolling」ボタンを表示する
    - ３行以下の動きを確認できたら：
        - 自動スクロールをストップさせない
- ユーザーが「Resume Auto Scrolling」ボタンを押したら
    - 自動スクロールを開始する
    - 「Resume Auto Scrolling」ボタンを非表示される

具体的なコードを教えかねますが、上記の流れで実装してみたら、「自動スクロール」と「スクロールバー」の場合も区別できて、機能はほぼ完成の状態でした。

### 微調整

紹介した`アルゴリズム1`の懸念点は、ユーザーが３行以下スクロールしたら、自動スクロールが止まらないことでした。
マウスとタッチパッドでスクロールする場合だと、この状態を簡単に改善できます(実装アイデア1で紹介した「スクロールのイベントリスナー」一覧をご覧ください)。

下記のアルゴリズムを追加しました。

**アルゴリズム２：**
- onWheelとonTouchMoveイベントリスナーを字幕が入っているdivに追加する。
```typescript
<div
  onScroll={onScroll} // アルゴリズム１で追加したもの
  onWheel={onUserManualScroll}
  onTouchMove={onUserManualScroll}
>
  字幕
</div>
```

- 上記のonUserManualScrollの定義：イベントリスナーが発火されたら、自動スクロールを停止し、「Resume Auto Scrolling」ボタンを表示する。

それのおかげで、ユーザーがマウスかタッチパッドで１行でもスクロールしても、すぐに「Resume Auto Scrolling」ボタンが表示されて、自動スクロールが止まります。

### 最後に

簡単そうに見えた機能でしたが、思ったより実装が面白かったです。

是非、グロービス学び放題でこの機能を使ってみてください！