[JavaScript]ランダムでホロライブのメンバーを表示させるGIFルーレットのようなものを作ってみた

みなさんお疲れ様です。Kosukeです。

今回は「スタートボタンを押すとホロライブメンバーがルーレットで表示され、ストップを押したタイミングで1人のホロメンが表示される」というWebアプリを作っていきます。

以下が制作したWebアプリです。

https://ronan0412.github.io/holo-choice/

![intro.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/938190/df4b4a00-d726-b8e4-ea22-b60d9ca72c74.jpeg)


# 目次
[1. 要件](#1-要件)
[2. HTMLを用意する](#2-htmlを用意する)
[3. JavaScriptで実装する](#3-javascriptで実装する)
[4. CSSを用意する](#4-cssを用意する)
[5. Try me!](#5-try-me)

# 1. 要件

【期待する動作】
1) スタートボタンを押す
2) ホロライブメンバーの数だけ、ルーレットのように立ち絵が表示される
3) ストップボタンを押すと、そのホロメンの紹介が表示される

# 2. HTMLを用意する
```html:index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>今日のホロメン</title>
  <script type="text/javascript" src="holomem.js"></script>
  <link href="./style.css" rel="stylesheet" type="text/css" media="all">
</head>
<body>
  <h1>今日のホロメンを選ぶ</h1>
  <button class="start-button" type="button" onclick="start()">START</button>
  <button class="stop-button" type="button" onclick="pickHolomem()">STOP</button>
  <br>
  <h2><div id="holomem-greeting"></div></h2>
  <img id="holomem-picture" src="https://hololive.hololivepro.com/wp-content/themes/hololive/images/head_l_in.png">
  <script type="text/javascript" src="script.js"></script>
  <h1 id="holoname-jp"></h1>
  <h1 id="holoname-en"></h1>
</body>
</html>
```

![スクリーンショット 2024-01-16 23.41.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/938190/1dd86d84-fb9e-94e0-bc4f-441a704a85b7.png)


HTMLでは、id属性やclass属性がついた複数の要素をシンプルに並べています。

ホロライブのロゴが表示されている部分の画像は、後のJavaScriptでホロメンの画像に置き換えます。

注意点としては、ホロメンの情報を記載したファイルである「holomem.js」をあらかじめ読み込んでおくことです。

また、button要素をクリックした際に実行するonclick属性の関数（startとpickHolomem)の書き方も注意が必要かと思いました。

# 3. JavaScriptで実装する
#1 ホロライブメンバーのオブジェクトを定義する

→オブジェクトを値とした配列を定義し、各オブジェクトでホロメンの名前や挨拶などの情報をまとめます。

※行が長くなってしまうため、0期生のみ記載しています。

※実際のリポジトリには直接ホロライブメンバーの画像を保存し、https通信による遅延が発生しないようにしています。

しかし、本記事ではコードをコピペして遊べるように、以下のJavaScriptではhttps通信で画像を取得するようにしています。
```javascript:holomem.js
let holomem = [];
holomem = [
    {
        JPname:"ときのそら",
        ENname:"Tokino Sora",
        group:"hololive JP",
        gen:"0",
        greetings:"こんそめ〜",
        image:"https://hololive.hololivepro.com/wp-content/uploads/2021/04/unnamed-file-6-855x1536.png"
    },
    {
        JPname:"ロボ子さん",
        ENname:"Robocosan",
        group:"hololive JP",
        gen:"0",
        greetings:"はろーぼー！",
        image:"https://hololive.hololivepro.com/wp-content/uploads/2020/06/roboco-san_pr-img_06.png"
    },
    {
        JPname:"AZKi",
        ENname:"AZKi",
        group:"hololive JP",
        gen:"0",
        greetings:"こんあずき〜！",
        image:"https://hololive.hololivepro.com/wp-content/uploads/2022/12/AZKi_pr-img_04-724x1440.png"
    },
    {
        JPname:"さくらみこ",
        ENname:"Sakura Miko",
        group:"hololive JP",
        gen:"0",
        greetings:"にゃっはろ〜！",
        image:"https://hololive.hololivepro.com/wp-content/uploads/2023/02/Sakura-Miko_pr-img_01-1008x1440.png"
    },
    {
        JPname:"星街すいせい",
        ENname:"Hoshimachi Suisei",
        group:"hololive JP",
        gen:"0",
        greetings:"彗星の如く現れたスターの原石！アイドルVTuberの星街すいせいです！",
        image:"https://hololive.hololivepro.com/wp-content/uploads/2023/04/bg_Hoshimachi-Suisei_01-1-925x1440.png"
    }
]
```

#2 機能を実装していきます。
```javascript:script.js
'use strict';

let done = 0;
let intervalID = 0;
// 1) スタートボタンを押す
function start() {
    //1回目の呼び出し
    if (done === 0) {
        // "2)" を100ミリ秒間隔で実行することで、ルーレットのように表示させる
        intervalID = setInterval(randomHolomem, 100);//n > 0
        done = 1;//実行済み
        console.log("初回の実行");
        //ルーレットのように立ち絵を表示する間は名前などを表示させない
        document.getElementById("holomem-greeting").innerHTML = "";
        document.getElementById("holoname-jp").innerHTML = "";
        document.getElementById("holoname-en").innerHTML = "";
    //2回目以降の呼び出し
    } else {
        console.log("2回目以降の実行");
        return;//2回目以降スタートを押された場合は処理を終わらせる
    }
}

let i = 0;
// 2) ランダムに立ち絵を表示させる
function randomHolomem() {
    i = Math.floor(Math.random() * holomem.length);
    document.getElementById("holomem-picture").src = holomem[i].image;
    return i;
}

let pickedIndex = 0;
// 3) ストップボタンを押す
function pickHolomem() {
    // "2)" の繰り返しを停止する
    clearInterval(intervalID);
    // ストップボタンが押され "2)" の繰り返しが停止している場合
    if (intervalID === 0) {
        return;//2回目以降ストップを押された場合は処理を終わらせる
    // スタートボタンを押して "2)" の繰り返しが続いている場合
    } else if (intervalID !== 0 && typeof intervalID === "number") {
        pickedIndex = randomHolomem();//ランダムに選ばれたホロメンのindex
        //選ばれたホロメンの画像や名前などを表示する
        document.getElementById("holomem-picture").src = holomem[pickedIndex].image;
        document.getElementById("holomem-greeting").innerHTML = "「" + holomem[pickedIndex].greetings + "」";
        document.getElementById("holoname-jp").innerHTML = holomem[pickedIndex].JPname;
        document.getElementById("holoname-en").innerHTML = holomem[pickedIndex].ENname;
    }
    intervalID = 0;//スタート-->ストップ-->ストップ...と押された場合を回避
    done = 0;//スタート-->ストップ-->スタート...と押された場合を回避
}
```

start()関数が "START" ボタンを押した際に実行される処理をまとめています。randomHolomem()という関数でランダムにホロライブメンバーをピックアップしますが、その処理をsetInterval()関数で繰り返すことで、ルーレットのように描画しています。

setInterval()関数の第二引数で指定したミリ秒の間隔で第一引数に指定した関数が実行されるため、今回の場合だと毎秒10回ランダムなホロメンが表示され、ルーレットのような見た目を作り出しています。

![start.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/938190/098eca65-3c3a-7226-2702-418579c020f2.gif)


※ホロメンの画像をhttps通信でカバー社のサーバーから取得すると、1秒間に10回https通信を繰り返して画像を表示していることになり、どうしても表示が遅延してしまいます。

そして pickHolomem()関数が "STOP" ボタンを押した際に実行される処理をまとめています。

初めに clearInterval()という関数を実行していますが、ここで「setInterval()で繰り返し処理を開始した際に発行される intervalID」をclearInterval() へ渡すことで、該当する繰り返し処理を停止させることができます。

その後は、"STOP" ボタンを押された段階で実行されるrandomHolomem()関数によって、取得されたランダムなホロメンの名前や挨拶を表示する流れとなります。

![stop.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/938190/d8c5a742-3568-955c-1796-a55ca7695d44.gif)

ここまでが大まかな処理ですが、"done(実行済み)" マーカーや条件分岐など、今回の処理を実現させるために必要なコードも多々あります()ので、一からコードを読んでみたりマーカー無しで実行させてみたりして存在意義を私に教えてください。

# 4. CSSを用意する

```css:style.css
button {
    color: #f0f8fc;
    background-color: #39ABE0;
    border: 0;
    border-radius: 10px;
    padding: 0 20px;
    line-height: 2.5;
    box-shadow: inset -2px -2px 3px #39ABE0, inset 2px 2px 3px #87cded;
}

button:hover {
    color: #ffffff;
}
button:active {
    box-shadow: inset -2px -2px 3px #87cded, inset 2px 2px 3px #39ABE0;
}
```

今回はボタンの装飾を実装しています。

一番初めのbuttonセレクタに記述されているbox-shadowプロパティでは、各ボタンに影を付けています。影のあり無しを比較してみると分かりやすいのですが、影ありのボタンは立体的に見えます。

<影なし>
![スクリーンショット 2024-01-18 22.16.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/938190/e13e6c1e-f88d-cfb6-3c20-8ed4dc794a6f.png)

<影あり>
![スクリーンショット 2024-01-18 22.16.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/938190/457771fe-3004-ea91-a4c4-338577575365.png)


疑似クラス :hover の部分では、カーソルがボタン要素の上に乗った際にボタンの文字を白く変化させます。

![FunTech2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/938190/75f1453c-37c8-da8d-3039-0b6a6db6246a.gif)

色が薄めでわかりにくいかもしれませんが、ボタンにカーソルを当てると "START" の色が濃くなり「カーソルが当たっていてクリックできる」ことが強調されます。

疑似クラス :active の部分では、ボタンをクリックした際の表示を指定しています。
一番初めにボタンのbox-shadowプロパティで左上に影を付けていましたが、ボタンをクリックしたタイミングで、左上の影を消して右下に影を付けるようにしています。

それによって、少しボタンの角度が変わったように見えて「クリックされた感」を描くことができます。

![button-active.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/938190/2095dd98-6243-ed78-90d8-ef16b4c09b91.gif)

# 5. Try me!
通しで遊んでみましょう。
冒頭のGitHub pagesからアクセスするかコードをローカルにコピペして遊んでみましょう！

![fixed.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/938190/f9b932f3-701b-7ce5-c300-9d6d5b782d81.gif)

X(旧Twitter)で見かけるGIFのような安定した美しいルーレットに見えるようにするには、やはりJavaScript等のファイルとホロメンの画像を同じサーバーに置いてネットワーク通信無しで画像を取得させることがポイントでした。

そのため、元のリポジトリではhttps通信ではなくローカルの画像を表示させるように修正しており、ところどころ載せているGIFもローカルから画像を取得しているため動作が速くなっています！

ということで、ここまで見ていただきありがとうございました！
遊んでみたコメントやアドバイスなどいただけますと喜びます！