DIY トークテーマスロット

## 概要
- 部内の雑談で使用する目的でトークテーマ生成ツールを作成
- お題にバリエーションを持たせるために、スロット形式を採用
- スロットアニメーションのイージングを工夫
- 本稿では、スロットの動作説明、イージングについて解説

## できたもの
### 実装した機能
- スロットのスライドアニメーション
- 「GO」ボタンを押すたびに、予め用意した文字列の配列から、スロットのリールを生成
- リールの順番をランダム化
![20221202.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985507/3fd85556-18fe-56f0-36ee-07779eb2c6d6.gif)

## はじめに

### 自己紹介
私は今年(2022年)の9月、ニジボックスに、フロントエンドエンジニアとして入社いたしました。
Web業界は、ほぼ未経験です。
現在は、HTML(pug), SCSS, Vanilla JavaScriptを業務で使用しています。

### 作成経緯
私が所属しているチームでは、「井戸端会議」と称して、
メンバー同士が、メンバーそれぞれのタスクの情報共有や、軽い雑談を行う場が設けられています。
雑談のお題を決定するのに、web上のトークテーマ生成ツールを利用していました。
ところが最近お題が被り、おととい話した話題で今日も雑談する、ということが起こるようになりました。
雑談のマンネリ化を防ぐため、また自身の自学のために、トークテーマ生成ツールの制作を開始しました。

### なぜスロットか
実は私の同期の方もトークテーマ生成ツールの制作を検討されていました。
その方から「文章を構成する各単語（名詞、形容詞など）をいくつか登録し、ランダム表示すれば実装できそう」というアイデアをいただきました。
通常の円形ルーレット表示でこれを実現しようとすると、画面がかなり大掛かりになりそうでしたので、
比較的コンパクトに収まるスロット表示にしようと考えました。

## スロットアニメーション
スロットの動作自体は非常に単純です。
リールの構成要素(今回は`span`)をflexboxで縦並びに整列させます。
それを`translormY()`関数で上下に移動させています。
以下のデモでは、「GO」ボタンを一度押すと枠内の数字が下方向にスライドして最終的に「9」が表示されます。
もう一度「GO」ボタンを押すと枠内の数字は「0」に戻ります。
<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="poKxOGj" data-user="t_kokubo" style="height: 300px; 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/t_kokubo/pen/poKxOGj">
  Slot_1</a> by kokubo (<a href="https://codepen.io/t_kokubo">@t_kokubo</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## リール追加
スロットに任意の文字列を表示するために、スロットアニメーションの手前でリールの構成要素を追加する処理を実装します。
以下のデモは[スロットアニメーション](#スロットアニメーション)と同じ挙動です。
しかし、初期状態で存在するリールの構成要素は「0」のみです。
「GO」ボタンを押すと1~9までの数字の`span`要素が先頭から、最終的に降順になるよう追加されていきます。
![20221205.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985507/a00b850d-78b8-4ca7-2edf-0c65d03011e3.gif)

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="gOKBQvW" data-user="t_kokubo" style="height: 300px; 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/t_kokubo/pen/gOKBQvW">
  Slot_2</a> by kokubo (<a href="https://codepen.io/t_kokubo">@t_kokubo</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

### putText()
`element.insertAdjacentHTML`メソッドを利用して、リールの構成要素を追加します。
今回は、スロットの文字のスライド方向が下です。
そうなるとリールの構成要素は、初期表示の文字の上に追加していく必要があります。
従って、第一引数`position`を`"afterbegin"`に設定しています。

https://developer.mozilla.org/ja/docs/Web/API/Element/insertAdjacentHTML

```js:putText()
// リール用のコンテナ
const content = document.querySelector(".itemInner");
// スロットの数字
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];

const putText = () => {
  for (let i = 0; i < numbers.length; i += 1) {
    content.insertAdjacentHTML(
      "afterbegin",
      `<span class="itemText">${numbers[i]}</span>`
    );
  }
};
```

### removeText()
`putText()`のみでは、「GO」ボタンを押す度にリールの構成要素が重複して追加されてしまいます。
そのため、`putText()`の手前で、リールコンテナ内の要素を削除する処理を実装します。
このとき、初期状態で表示する数字まで削除されることを防ぐため、
追加削除される数字の要素とはそれぞれクラス(あるいはdata属性)を分けておきます。
```js:removeText()
// リール用のコンテナ
const content = document.querySelector(".itemInner");
// スロットの数字
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];

const removeText = () => {
  const elements = content.querySelectorAll(".itemText");
  elements.forEach((element) => {
    element.remove();
  });
};
```

## リールの順をランダム化
スロットをリール内の要素の順番をランダム化する処理を実装します。
具体的には、予め用意されたリール用文字列の配列から
配列内要素をシャッフルした配列を新たに生成します。
この配列を先ほどの`putText（）`に引数として渡します。

<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="yLEGymy" data-user="t_kokubo" style="height: 300px; 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/t_kokubo/pen/yLEGymy">
  Untitled</a> by kokubo (<a href="https://codepen.io/t_kokubo">@t_kokubo</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

配列内要素をシャッフルする処理は以下サイトを参考にさせていただきました。

https://zenn.dev/k_kazukiiiiii/articles/cf3256ef6cbd84


## イージング
スロットのアニメーションに面白味を持たせるために、CSSの`transition`を調整します。
`transition-timing-function`プロパティは`"ease-in"`、`"ease-out"`などのキーワード値の他に、
`cubic-bezier(0.1, 0.7, 1.0, 0.1)`のように関数値を設定することができます。これを使用して、より高度なイージング設定ができます。

https://developer.mozilla.org/ja/docs/Web/CSS/transition-timing-function

```css:キーワード値でのイージング設定
{
transition: transform 1.5s ease-in-out;
}
```
```css:関数値でのイージング設定
{
transition: transform 1.5s cubic-bezier(0.63, -0.25, 0.41, 1.24);
}
```
関数の曲線の選定には以下のサイトを参考にしました。

https://easings.net/ja

リールの回転開始、終了の両タイミングで逆方向に反動をつけたいと考えました。
今回は、それを再現可能な「easeInOutBack」関数を使用しました。

https://easings.net/ja#easeInOutBack

また、関数の曲線を調整するにあたり、[cubic-zezier](https://cubic-bezier.com/)というwebサイトを利用しました。

https://cubic-bezier.com/

本サイトは、ベジエ曲線をGUIで編集し、cubic-bezier記法のコードで出力してくれます。
実際のイージングの様子も、スライドアニメーションで確認できるので大変便利です。
![20221205-1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2985507/5f90bf83-25fd-df3d-7135-2ecb47221c7e.gif)


イージングを設定した最終的なデモを以下に示します。
あとは、お題を生成するために必要な単語群の数だけスロットコンテナを用意し、
それぞれの文字列の配列を用意すればトークテーマスロットの完成です(動きは[できたもの](#できたもの)のパートをご覧ください)。
<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="KKebpvV" data-user="t_kokubo" style="height: 300px; 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/t_kokubo/pen/KKebpvV">
  Slot_4</a> by kokubo (<a href="https://codepen.io/t_kokubo">@t_kokubo</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## 今後の取り組み
今回は、スロットの動きを固定化する(ユーザーの任意操作ではなく、勝手にストップする仕様にした)ことで実装内容を単純化することができました。
今後は以下の機能実装に挑戦したいと考えています。
- スタート、ストップ機能の実装
    - 任意のタイミングでスロットを止めることができるようにしたいと考えています。
- 生成されたお題の文章を自然にする
現状は用意された単語をランダム出力するだけなので、最終的な文章がチグハグになる時があります。
各単語の語尾を自動で調整して文章が自然になるようにしたいと考えています。
（フロントエンド外の技術が大量に必要になってきてかなり難易度高そうですが…）

## 謝辞
アドベントカレンダーの記事内容の相談に乗ってくださった先輩社員の方、
トークテーマスロットのアイデアをご提供下さった同期の方、
この記事の添削をしてくださったレビュワーの方々に感謝申し上げます。