ルパン三世のカシャカシャってなるタイトルコールを好きな文字列で生成する


はじめに

ルパン三世のアニメのタイトルコールって独特ですよね。
タイプライターのようなカシャカシャって音とともに、かっこよくタイトルが現れます。

<参考↓>
https://www.youtube.com/watch?v=StZfAP64bfU

これ、自分で作れないかな?と思って、三連休溶かして色々調べてたら

思いの外簡単にできちゃいました。

※実際は音も出ます(Qiitaに動画貼れるようにしてほしい)
※GIFなので少し速く見えますが、実際はもうちょっとゆっくりです。

どんなのができたのよ

こんなのです。
https://tol0929.github.io/LupinTitleCall/

音声は↓を使ってます。
https://www.youtube.com/watch?v=StZfAP64bfU

動作環境

Chromeでは比較的安定して動きます。ていうかChrome以外だと音がずれてしまってうまくいきません。
スマホ未対応です。PCから御覧ください。

Macしか見てませんが、たぶんWindowsでも大丈夫だと思います。

ソース

全容はGithubにて。
主要部分のHTMLとJSだけ載せます。
https://github.com/tol0929/LupinTitleCall

index.html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta charset="utf-8" />
<link rel="stylesheet" href="./style.css">
<title>ルパン三世風タイトルコールジェネレータ</title>
</head>
<body>
    <div id="show" class="showArea"></div>
    <div class="inputArea">
        <input type="text" id="strValue">
        <input type="button" value="とっつぁ〜ん!" onclick="showString();">
    </div>
    <script src="./main.js"></script>
</body>
</html>
main.js
let audioElem;
const BGM = "./mp3/bgm.mp3";
const KASYA = "./mp3/kasya.mp3";

// 200ミリ秒待つ
const sleep = () => {
    return new Promise((resolve) => setTimeout(resolve, 200));
}

// mp3ファイルの音声を再生
const playSound = (filename) => { 
    audioElem = new Audio();
    audioElem.src = filename;
    audioElem.play();
}

const showString = async () => {
    const elm = document.getElementById("show");
    const str = document.getElementById("strValue").value;

    elm.style.textAlign = "center";
    elm.style.fontSize = "200px";

    // 音とともに一文字ずつ表示
    for(let i = 0; i < str.length; i++){
        elm.innerHTML = "";
        elm.innerHTML = str[i];
        playSound(KASYA);

        // 200ミリ秒待つ
        await sleep();
    }
    // 全文表示
    elm.innerHTML = str;
    elm.style.textAlign = "left";
    elm.style.fontSize = "50px";
    playSound(BGM)
}

少し苦労したところ

ふざけてばかりだと怒られそうなので、少し技術的なお話を。

javascriptで非同期処理を書かないといけない場面があったんですが、今まで非同期処理を0から実装したことがなかったのでちょっと苦戦しました。(他人が書いた非同期処理をいじったことしかなかった)

「タイトル文字列を1文字出力」

「200ミリ秒待つ」

「次の1文字を出力」

「200ミリ秒待つ」

みたいな感じでやりたかったので、最初何も考えずに↓のコード書いてました。

ダメな書き方.js
const elm = document.getElementById("show");
const str = document.getElementById("strValue").value;//出力する文字列

for(let i = 0; i < str.length; i++){
    setTimeout(() => {
        elm.innerHTML = "";
        elm.innerHTML = str[i];
        playSound(KASYA);//カシャカシャって音を再生
    }, 200);
}

これだと、一回目のループでだけ200ミリ秒待ってくれますが2回目以降では待ってくれません。
async/awaitを使って↓のように書き換えましょう。

正しくはこう.js

// 200ミリ秒待つ
const sleep = () => {
    return new Promise((resolve) => setTimeout(resolve, 200));
}

const showString = async () => {
    const elm = document.getElementById("show");
    const str = document.getElementById("strValue").value;

    // 音とともに一文字ずつ表示
    for(let i = 0; i < str.length; i++){
        elm.innerHTML = "";
        elm.innerHTML = str[i];
        playSound(KASYA);

        // 200ミリ秒待つ
        await sleep();
    }
}

promiseを絡めた非同期処理関数をasync関数の中でawaitすることで、解決できました。
名前が直感的でわかりやすかったです。

追記

別解

コメントで指摘いただいたのですが、以下コードだと非同期処理を使わずに期待動作を実装できます。

非同期処理を使わない.js
const elm = document.getElementById("show");
const str = document.getElementById("strValue").value;//出力する文字列

for(let i = 0; i < str.length; i++){
    setTimeout(() => {
        elm.innerHTML = "";
        elm.innerHTML = str[i];
        playSound(KASYA);//カシャカシャって音を再生
    }, 200 * (i + 1));/* i+1をかける!! */
}

最初のだめな書き方.jsの最後から2行目を変えただけ。

だめな書き方.jsの問題点は200ミリ秒数える起点が常にi = 0の時点になっていたこと。
なので、setTimeoutの第二引数にi + 1をかけて200, 400, 600, ...と増やしていくことで200ミリ秒刻みで数えているのと同義になります。

派生版が作られた

こんな勢いで作っただけのネタアプリを派生してくださる人が現れました。

・タイトル画面のサイズを可変にしたもの
ルパン三世のカシャカシャってなるタイトルコールのやつをsvgにしたかった

・ブラウザサイズいっぱいいっぱいまで広げたもの
Lupin The Third Title Call

参考

【JS】setTimeoutを用いた、非同期処理入門
async/await 入門(JavaScript)