メッセージウィンドウの作り方

以下のメッセージウィンドウの作り方を紹介します。

フォルダー構成

以下の構成でフォルダーを作成し、その下にファイルを置きます。

    sample01
    • sample01.html

    • css
      • sample01.css

    • js
      • MessageWindow.js

      • Loop.js

      • sample01.js

HTML

sample01.html

 <!DOCTYPE HTML>
<html lang='ja'>
<head>
    <meta charset='utf-8'>
    <meta name="viewport" content="width=device-width" />
    <title>メッセージウィンドウの作り方</title>
    <link rel="stylesheet" href="css/sample01.css" type="text/css" />
    <script src="js/MessageWindow.js"></script>
    <script src="js/Loop.js"></script>
    <script src="js/sample01.js"></script>
</head>
<body>
    <div class="メイン画面">
        <div class="メッセージウィンドウ ウィンドウ">
            <p class="テキストエリア"><span class="メッセージ"></span><span class="カーソル">■</span></p>
        </div>
    </div>
</body>
</html> 

14〜16行目がメッセージウィンドウです。

CSS

sample01.css

 body,div,p,td,th {
    margin: 0;
    padding: 0;
}

.メイン画面 {
    width: 640px;
    height: 200px;
    position: relative;
    background-color: rgba(0, 0, 0);
    margin: 20px auto;
}

.ウィンドウ {
    position: absolute;
    background-color: rgba(0, 0, 255, 0.7);
    border: solid 2px white;
    border-radius: 10px;
    padding: 15px 20px;
    margin: 10px;
}

.テキストエリア {
    font-size: 1.5rem;
    color: white;
    text-shadow: 2px 2px 2px #000, -2px -2px 2px #000, -2px 2px 2px #000, 2px -2px 2px #000, 2px 0px 2px #000, -2px -0px 2px #000, 0px 2px 2px #000, 0px -2px 2px #000;
    letter-spacing: 0.07em;
}

.メッセージウィンドウ {
    width: 576px;
    height: 146px;
    overflow: auto;
}
    .メッセージウィンドウ .カーソル {
        visibility: hidden;
    } 

メッセージウィンドウのレイアウトや色、文字サイズついて設定しています。

JavaScript

MessageWindow.js

 class メッセージウィンドウ {
    constructor(メッセージウィンドウ要素, メッセージ要素, クリック待ち要素, 表示待ち間隔 = 1000 / 60, クリック待ち点滅間隔 = 1000 / 4) {
        this.メッセージウィンドウ要素 = document.querySelector(メッセージウィンドウ要素);
        this.メッセージ要素 = this.メッセージウィンドウ要素.querySelector(メッセージ要素);
        this.クリック待ち要素 = this.メッセージウィンドウ要素.querySelector(クリック待ち要素);
        this.表示待ち間隔 = 表示待ち間隔;
        this.クリック待ち点滅間隔 = クリック待ち点滅間隔;
        this.クリック待ちフラグ = false;
        this.メッセージ = "";
        this.メッセージ位置 = 0;
        this.表示メッセージ = "";
        this.メッセージウィンドウ要素.onclick = this.クリック時の処理を行う.bind(this);
        this.累計時間 = 0;
    }

    メッセージを表示する(id, メッセージ, 追加フラグ = false) {
        return new Promise(resolve => {
            if (追加フラグ) {
                this.メッセージを追加する(メッセージ);
            } else {
                this.メッセージを設定する(メッセージ);
            }
            ループ.更新関数を追加する(id, (経過時間) => {
                if (this.クリック待ちフラグ) {
                    this.クリック待ちサインを表示する(経過時間);
                    return;
                }
                if (this.メッセージ位置 == this.メッセージ.length) {
                    ループ.更新関数を削除する(id)
                    resolve();
                    return;
                }
                this.表示する(経過時間);
            });
        });
    }

    メッセージを設定する(メッセージ) {
        this.メッセージ = メッセージ;
        this.メッセージ位置 = 0;
        this.表示メッセージ = '';
        this.メッセージ要素.innerHTML = '';
    }

    メッセージを追加する(メッセージ) {
        this.メッセージ += メッセージ;
    }

    表示する(経過時間) {
        this.累計時間 += 経過時間;
        if (this.表示待ち間隔 > this.累計時間) {
            return;
        }
        this.累計時間 -= this.表示待ち間隔;
        if (this.累計時間 > 0) this.累計時間 = 0;

        if (this.メッセージ.charAt(this.メッセージ位置) == '<') {
            if (this.メッセージ.substring(this.メッセージ位置, this.メッセージ位置 + 4) == '<br>') {
                this.表示メッセージ += '<br>';
                this.メッセージ位置 += 4;
                return;
            } else if (this.メッセージ.substring(this.メッセージ位置, this.メッセージ位置 + 3) == '<c>') {
                this.表示メッセージ = '';
                this.メッセージ位置 += 3;
                return;
            } else if (this.メッセージ.substring(this.メッセージ位置, this.メッセージ位置 + 3) == '<w>') {
                this.クリック待ちフラグ = true;
                this.メッセージ位置 += 3;
                return;
            }
        }

        this.表示メッセージ += this.メッセージ.charAt(this.メッセージ位置);
        this.メッセージ位置++;
        this.メッセージ要素.innerHTML = this.表示メッセージ;
        this.メッセージウィンドウ要素.scrollTop = this.メッセージウィンドウ要素.scrollHeight;
    }

    クリック待ちサインを表示する(経過時間) {
        this.累計時間 += 経過時間;
        if (this.クリック待ち点滅間隔 > this.累計時間) {
            return;
        }
        this.累計時間 -= this.クリック待ち点滅間隔;

        if (this.クリック待ち要素.style.visibility == 'hidden') {
            this.クリック待ち要素.style.visibility = 'visible';
        } else {
            this.クリック待ち要素.style.visibility = 'hidden';
        }
    }

    クリック時の処理を行う(event) {
        this.クリック待ちフラグ = false;
        this.クリック待ち要素.style.visibility = 'hidden';
    }
} 

2行目のコンストラクタの1番目の引数は、メッセージウィンドウ要素(div要素)のセレクターを指定します。
2番目の引数は、メッセージウィンドウ要素の配下にあるメッセージを表示させる要素(span要素)のセレクターを指定します。
3番目の引数は、メッセージウィンドウ要素の配下にあるクリック待ちを表す要素(span要素)のセレクターを指定します。
4番目の引数は、メッセージを1文字づつ表示させる間の待ち時間を指定します。
省略した場合は1/60秒間隔で表示します。
5番目の引数は、クリック待ちを表す要素の点滅間隔を指定します。
省略した場合は1/4秒間隔で表示します。

16行目のメッセージを表示する関数の1番目の引数は、ループクラスからコールする関数のidを任意の値で指定します。
2番目の引数は、表示させたいメッセージを指定します。
メッセージ中で改行をしたい場合は<br>、クリアしたい場合は<c>、クリック待ちにしたい場合は<w>を記述します。
3番目の引数は、既に表示しているメッセージに追記したい場合にtrueを指定します。
省略した場合は既に表示しているメッセージをクリアして表示します。
17行目では、Promiseのオブジェクトを作成することでメッセージの表示処理を非同期で実行しています。
非同期にするメリットは、メッセージを表示し終わった時にコールするコールバック関数が不要なため、コールバック地獄が起こらずコードの可読性が上がることです。
23行目では、ループクラスから定期的にコールさせたい関数(23〜34行目)を追加しています。
28行目で、メッセージを全て表示し終えると、23行目でループクラスに追加した関数を削除しています。
30行目で、非同期処理を終了しています。

Loop.js

 class ループ {
    static 更新関数リスト = [];
    static 時間 = { 過去: 0, 現在: 0, 経過: 0 };

    static 開始する() {
        ループ.更新する();
        requestAnimationFrame(ループ.開始する);
    }

    static 更新する() {
        ループ.時間.現在 = Date.now();
        ループ.時間.経過 = ループ.時間.現在 - ループ.時間.過去;
        ループ.時間.過去 = ループ.時間.現在;
        ループ.更新関数リスト.forEach(x => {
            x.更新関数(ループ.時間.経過)
        });
    }

    static 更新関数を追加する(id, 更新関数) {
        ループ.更新関数リスト.push({ id, 更新関数});
    }

    static 更新関数を削除する(id) {
        ループ.更新関数リスト = ループ.更新関数リスト.filter(x => x.id != id);
    }
} 

Loopクラスは追加した更新関数を一定時間ごとにコールします。

sample01.js

 class Sample01 {
    static async main() {
        Sample01.mw = new メッセージウィンドウ(".メッセージウィンドウ", ".メッセージ", ".カーソル", 1000 / 24);
        ループ.開始する();

        await Sample01.mw.メッセージを表示する("メッセージ", "むかしあるところに<br>お爺さんとお婆さんが住んでいました<br><w>");
        await Sample01.mw.メッセージを表示する("メッセージ", "お爺さんは山に芝刈りに<br>お婆さんは川に洗濯に行きました<w>",true);
        await Sample01.mw.メッセージを表示する("メッセージ", "お婆さんが川で洗濯をしていると<br>川上から大きな桃が流れてきました<w>");
        await Sample01.mw.メッセージを表示する("メッセージ", "めでたしめでたし?");
    }
}

addEventListener('load', Sample01.main); 

13行目でsample01.htmlの読み込みが終わると、Sample01クラスのmain関数をコールするように指定しています。
3行目で、メッセージウィンドウクラスのオブジェクトを作成しています。
3番目の引数に1000/24と指定しているので、1/24秒毎に1文字づつ表示します。
4行目で、ループを開始します。
6〜9行目で、メッセージウィンドウにメッセージを表示しています。

2024年04月19日

この記事へのコメント
コメントを書く

お名前:

メールアドレス:


ホームページアドレス:

コメント:

この記事へのトラックバックURL
https://fanblogs.jp/tb/12516552
※ブログオーナーが承認したトラックバックのみ表示されます。

この記事へのトラックバック
Mobilize your Site
スマートフォン版を閲覧 | PC版を閲覧
Share by: