新規記事の投稿を行うことで、非表示にすることが可能です。
2024年04月19日
メッセージウィンドウの作り方
以下のメッセージウィンドウの作り方を紹介します。
■
フォルダー構成
以下の構成でフォルダーを作成し、その下にファイルを置きます。
-
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行目で、メッセージウィンドウにメッセージを表示しています。