ブラウザで動く短い物語では、ストーリー全体よりも「いま何を選んだか」を明確に持つことが役立ちます。今回は四つの選択を持つUIを題材に、表示文章と選択状態を分ける最小のJavaScript例を紹介します。
着想の例は、非公式のファン作品 The Stolen Invitation の公開導入画面です。そこには任意の名前入力と「Four choices. One final bell.」という説明があります。以下のコードはこの記事のための独立した学習例であり、その作品の実装・実際の選択肢・ソースコードを再現したものではありません。
選択済みかどうかを文章から判定しない
文章そのものを状態として使うと、翻訳や表現の変更が判定条件に影響します。例では選択を A/B のIDとして保存し、表示文章は別に用意する前提にします。
function createDraft(alias = "") {
return {
alias: alias.trim().slice(0, 40) || "The Guest",
choices: [null, null, null, null],
};
}
function choose(draft, index, optionId) {
if (!Number.isInteger(index) || index < 0 || index > 3) {
throw new RangeError("index must be 0..3");
}
if (optionId !== "A" && optionId !== "B") {
throw new TypeError("optionId must be A or B");
}
return {
...draft,
choices: draft.choices.map((oldValue, i) =>
i === index ? optionId : oldValue
),
};
}
function isComplete(draft) {
return draft.choices.every((value) => value === "A" || value === "B");
}
let draft = createDraft("Guest");
draft = choose(draft, 0, "A");
draft = choose(draft, 1, "B");
draft = choose(draft, 2, "A");
draft = choose(draft, 3, "B");
console.log(isComplete(draft)); // true
この小さな例では、入力可能な位置と選択IDを関数の入口で限定しています。UIや将来の変更によって不正な位置や選択IDが渡されたときに、早く発見するためです。保存データを復元する場合の構造検証は、この例には含めていません。実際の作品では、選択肢IDを物語の設計に合わせて定義します。
更新前の状態を残す
choose は元の配列を変更せず、新しい配列を返します。更新前と更新後の差を確認できるので、「戻る」「やり直す」の設計でも、どの時点の状態を保存するかを決めやすくなります。この例自体には履歴機能は実装していません。
任意の名前はHTMLとして扱わない
名前を画面に表示する際は、プレーンテキストとして扱います。
nameElement.textContent = draft.alias;
名前を文字列連結した innerHTML に渡す必要はありません。また「端末内だけに保存」と説明するなら、URL、計測イベント、外部APIにも名前を送っていないか、別途確認が必要です。参照作品のネットワークや保存方式を監査したという意味ではありません。
確認する境界
- 負の位置、4以上の位置、小数の位置を拒否する。
- A/B以外の値を拒否する。
- 一つの選択で別の位置が変わらない。
- 更新前のドラフトが変更されない。
- 四つを選び終えるまで完成扱いにしない。
この記事の最初のコードブロックはNode.js v24.19.0で実行し、上記の入力境界、元の配列が変わらないこと、未完了と完了の判定を確認しました。DOMの表示例や参照作品全体をE2Eテストしたという意味ではありません。
大きなエンジンを先に作るより、まず選択状態を明示し、画面の文章から分離すると、短い物語でも検証箇所をはっきりさせられます。
開示:この記事はAIで作成した技術解説で、参照した非公式ファン作品を紹介する目的も含みます。コードは独立した学習例です。