conic-gradientでルーレット作ってみた

## 3行で
オンライン交流会のためにルーレット&サイコロトークのツール作ったよ
今回はルーレットの背景色の設定方法について記載するよ
実際のツールも載せてるからよかったら使ってみて感想もらえると嬉しいよ

## 前提
部内のオンライン交流会で参加者同士わいわいお喋りできるような企画を検討。
[サイコロトーク](https://nyaakoweb.com/chidokama-dicetalk/)に誰が話すか決めるルーレット機能も追加したら交流しやすそう。
→ルーレット機能+サイコロトーク機能があるツールを作ろう！となりました。
※今回の記事ではルーレットの背景色の設定方法のみ記載します。

### この記事を書いている人
2022年3月にほぼ未経験からフロントエンジニアとして働き始めている。
業務で主に使う言語はHTML、SCSS（cssの拡張言語）、時々JavaScript（JQuery）という感じ。

### 使った技術
開発環境はwebpackを使用。
今回はVanila JavaScriptのスキルを高めたかったので、JQueryは使わないことにした。

## そもそもconic-gradientとは？
うまく説明できる自信がないので、[MDNの該当ページ](https://developer.mozilla.org/ja/docs/Web/CSS/gradient/conic-gradient)の文章を引用します。
> CSS の関数で、 (中心から広がるのではなく) 中心点の周りを回りながら色が変化する画像を生成します。扇型グラデーションの例としては、円グラフや色相環などがあります。

上記に記載されている円グラフと色相環も上記MDNの該当ページにサンプルがあり、イメージつきやすくなると思うので載せておきます。

**円グラフ**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982124/664b5690-c87b-0fe6-6121-88d693c73179.png)

**色相環**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982124/c95f7201-5342-628c-e550-27aea7b34d40.png)

今回は**円グラフ**のイメージでルーレットを作っていきます。

## htmlとcssで3分割のルーレットを作ってみる
上記の円グラフの書き方にならって、円グラフを書いてみました。
以下の通りの記述をすることで、0度から120度は#ec9376、120度から240度は#ecbe75、240度から360度は#e3e986で表示することができます。
```css:conic-gradientの書き方例
.roulette__image {
    background: conic-gradient(
          #ec9376 0deg 120deg,
          #ecbe75 120deg 240deg,
          #e3e986 240deg 360deg
        );
}
```
<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="JjZOKPw" data-user="msuzuna" 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/msuzuna/pen/JjZOKPw">
  conic-gradientの使い方①</a> by msuzuna (<a href="https://codepen.io/msuzuna">@msuzuna</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## JavaScriptも使って動的に色分割を増減させてみる
3分割の初期値は上記の状態で実現できたので、次はJavaScriptを使って動的に分割個数を増減させてみました。
上記の状態から、色分割の個数を表示するラベルとUPボタン、DOWNボタンを追加してます。
UPボタンとDOWNボタンをクリックしてもらえれば、ラベルの数字とルーレットの分割個数が連動して変化しているのがわかるかと思います。
<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="QWxOKEJ" data-user="msuzuna" 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/msuzuna/pen/QWxOKEJ">
  conic-gradientの使い方①</a> by msuzuna (<a href="https://codepen.io/msuzuna">@msuzuna</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

JSどんな操作を行ったか簡単に記載します。
1. 円を分割する個数に応じて、1分割あたりの角度を計算するcalcAngle関数を定義
2. 分割個数分色コードが格納される配列を返すsetColorArray関数を定義
3. conic-gradientを設定するための文字列を返すsetCssText関数を定義
4. UPボタンやDOWNボタンが押された時に1~3の関数を使っていい感じにルーレットの分割個数が増減されるようにする

それぞれどのように考えて実装したのか書いてみます。

### calcAngle関数
こちらは単純ですが、個数を受け取ったら360をその個数で割って1個あたりの角度を算出し、その1個あたりの角度を返す関数です。
```JavaScript:calcAngle関数
const calcAngle = function (num) {
  const angle = 360 / num;
  return angle;
};
```

### setColorArray関数

ルーレットで必要なのは、「隣り合った部分の色が同じにならないこと」だと思ったので、

- 1個目の部分のみ色を固定（A色）
- 2個目以降はB色、C色、D色、E色が順番に割り当てられる

ようにすると何分割しても隣り合った部分の色が同じにならないと考えました。

```JavaScript:setColorArray関数
const setColorArray = function (num) {
  let colorArray = [];
  colorArray[0] = '#EC9376'; // 1個目の色を固定

  for (let i = 1; i < num; i++) {
    if (i % 4 === 1) { // 2,6,10...個目の場合
      colorArray[i] = '#ECBE75';
    } else if (i % 4 === 2) { // 3,7,11...個目の場合
      colorArray[i] = '#E3E986';
    } else if (i % 4 === 3) { // 4,8,12...個目の場合
      colorArray[i] = '#A9F0D1';
    } else if (i % 4 === 0) { // 5,9,13...個目の場合
      colorArray[i] = '#90CDFB';
    }
  }
  return colorArray; // 色カラーが格納されている配列を返す
};
```
### setCssText関数

最後に、実際にcssを設定する形に文字列を設定する関数を定義しました。
こちらはかなり力技感があるのですが、分割個数の1つ目から角度と色を代入して最終的に必要な文字列を得られるようにしました。

```JavaScript:setCssText関数
// 色を設定する文字列を返す関数
const setCssText = function (num, angle) {
  let backgroundText = '';
  const lastNum = num - 1;
  const colorArray = setColorArray(num);

  //最後の1回前まで
  for (let i = 0; i < lastNum; i++) {
    backgroundText =
      backgroundText +
      colorArray[i] +
      ' ' +
      angle * i +
      'deg ' +
      angle * (i + 1) +
      'deg,';
  }

  // 最後の1回
  backgroundText =
    backgroundText +
    colorArray[lastNum] +
    ' ' +
    angle * lastNum +
    'deg ' +
    angle * (lastNum + 1) +
    'deg';

  return 'conic-gradient(' + backgroundText + ')'; // backgroundに設定する文字列を返す
};
```

### ボタンが押されたときの処理

今まで定義してきた関数を使って、ボタンがクリックされたときの処理を記述します。
本来目指しているツールは、textAreaにテキストが入力されると分割個数が増減する、という機能なのですが一旦conic-gradientの動きをわかりやすく理解するためにこの記事ではbuttonでの実装を行いました。

```JavaScript:ボタンが押された時の処理
// UPボタンが押されたら
upButton.onclick = function() {
  const num = Number(label.innerHTML) + 1;
  label.innerHTML = num;
  const angle = calcAngle(num);
  const cssText = setCssText(num, angle);
  circle.style.background = cssText;
}

// DOWNボタンが押されたら
downButton.onclick = function() {
  const num = Number(label.innerHTML) - 1;
  label.innerHTML = num;
  const angle = calcAngle(num);
  const cssText = setCssText(num, angle);
  circle.style.background = cssText;
}
```

## 2022/11/18(金)時点実際に作ったツール
というわけで実際に作ったツール（試作段階）のものがこちらです。

https://msuzuna.github.io/dicetalk/


「もっとこうした方が良くなりそう！」などのご意見あればお気軽にコメントくださいmm

### ルーレット機能
実装できている機能は以下の通り。
- ラベル（人の名称）を表示
- ラベルの角度を適切に表示
- ルーレットの背景色とラベルがいい感じに表示されるように配置
- テキストエリアに名前を打ち込んだら分割個数が増える
- STARTボタンを押したらルーレットが回ってランダムな位置で止まる


改善の余地があるのは以下の通り。
- テキストエリアの名前を削除しても反映できない（deleteキーでのイベントが発火しない）
    - 削除した後Enterキーを押したら適切に表示される[^1]
- 全体的にデザインもいい感じになったらいいかもね



### サイコロトーク
実装できている機能は以下の通り。
- トークリストの削除できる
- トークリストの追加できる
- サイコロっぽいものを表示
- 振るボタンを押したらサイコロっぽいものが動いてランダムで数字を表示してくれる

改善の余地があるのは以下の通り。
- ちゃんとサイコロの目が表示できる方がいいかも
- サイコロの目で表示するならリストは6つまでしか追加できないようにしたほうがいいかも

## 作ってみての感想
ひとまずオンライン交流会でツール使えるだけの最低限の機能は実装できたので一安心。

実装に関してだと、conic-gradientは恐れていたほど難しいわけではなかったし、円グラフを簡単に表示できるのはめっちゃ良かった。

一番難しかったところはルーレットのラベルをいい感じに配置させること。
今回は一つの要素をルーレットの円として表示させて、その上にラベルを等間隔で表示させる方法でチャレンジしてみたところ、

- 要素を円形状に等間隔で配置するのに三角関数（sin,cosとかラジアンとか）を使うことになった
- ルーレットを回すときに行列の考え方が出てきた

という感じで、 **「数学」** の知識が必要になったのが地味に辛かった。

![DSC_0024.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2982124/8b941fa3-c910-8435-e9b3-0965f900884e.jpeg)
（試行錯誤の跡）


スキル向上を目的とした自主制作だと何を作っていいかわからない、何を作るか決めかねてしまってなかなか着手できないことがあるかと思うが、
今回は **「いつまでに」「何を目的として」** という部分が明確だったので取り組みやすかった。

今後もフロントエンジニアとしてのスキルを高めていくのはもちろんだが、何を作るかで悩みそうな時は「身近な困りごとはなんだろう？」「これがあればより便利になるものはなんだろう？」という思考で考えてみるのも良さそうだと思った。

[^1]: こちら先輩に指摘いただいていたのですが、deleteキーは発火できていました！
自分がdeleteキーだと思って押下していたキーはBackspaceキーだったので発火できていないと思い込んでました><
（超初歩的なミスで恥ずかしい）