CSSだけで作れるゲームUI:ボタン・ゲージ・ポップアップ
遊びをつくる執筆にClaudeを使用しています。

ブラウザゲームのUIは、JavaScriptがなくてもCSSだけで驚くほど多くの表現が実現できます。ボタンのホバーエフェクト、HPゲージのアニメーション、ゲームオーバー時のポップアップ——これらはすべてCSSのプロパティと疑似クラスを組み合わせることで動かせます。 「ゲームらしい見た目」を求めてJavaScriptのDOM操作から入ろうとすると、UIの装飾コードとゲームロジックが混ざりやすくなります。CSSに任せられる部分をCSSに分離しておくと、後からロジックを修正するときにUIが壊れにくくなり、コードの見通しも良くなります。 この記事では、実際にゲームUIとして使える3種類のパーツ——押し応えのあるボタン、値の変化を表すゲージ、画面をオーバーレイするポップアップ——の作り方を、コードの選択理由と一緒に説明します。
ゲームUIをCSSで作るときの基本的な考え方
CSSだけで「インタラクション」を表現するには、状態の変化をセレクターで捉える必要があります。使うのは主に3つです。
- 疑似クラス(:hover :active :focus):マウス・キーボードの操作に反応する
- 疑似要素(::before ::after):HTMLを増やさずに装飾用の要素を生成する
- CSS変数(--variable-name):JavaScriptから値を渡す接点として使う
ゲームUIで特に重要なのはCSS変数です。HPゲージの残量など「数値が変わる」表現は、JavaScriptから element.style.setProperty('--hp', '0.4') のように変数だけを書き換えることで、スタイル全体をCSSに書いたまま値だけ動的に変えられます。JavaScriptがレイアウトの計算に関与しないので、描画の責任が分離されます。
押し応えのあるゲームボタン
ゲームのボタンは「クリックした感覚」が大切です。フラットなボタンでは操作感が薄く、プレイヤーが押せたかどうか迷うことがあります。CSSで立体感と押し込みアニメーションを付ける方法を示します。
.game-btn {
position: relative;
display: inline-block;
padding: 12px 32px;
font-size: 1rem;
color: #fff;
background: #3a86ff;
border: none;
border-radius: 6px;
/* 下方向の影で「厚み」を表現 */
box-shadow: 0 6px 0 #1a56cc;
cursor: pointer;
/* 動きをまとめて transition で管理 */
transition: transform 80ms ease, box-shadow 80ms ease;
user-select: none;
}
.game-btn:hover {
background: #5a9aff;
}
.game-btn:active {
/* 押し込んだ分だけ下にずらし、影を消す */
transform: translateY(4px);
box-shadow: 0 2px 0 #1a56cc;
}box-shadow で下方向に色の付いた影を作るのがポイントです。:active のときに translateY でボタン本体を影の幅分だけ下げると、物理的に押し込まれたような見た目になります。transition の時間を80ms程度にすることで、即応性を感じさせつつアニメーションが見えます。
失敗しやすい点は border-bottom で厚みを表現しようとするパターンです。border-bottom は:activeで自然に消えないため、押し込み時に別途border-bottom: noneと高さの補正が必要になり複雑になります。box-shadow の方が一つのプロパティで制御できます。
アイコンや光沢を加えたい場合は ::after 疑似要素で対応できます。HTMLにタグを追加せずにボタン上部に半透明の白いグラデーションを重ねるだけで、ゲームらしい光沢感が出ます。
値の変化を伝えるHPゲージ
RPGやアクションゲームで頻出するゲージは、CSSの width と transition の組み合わせが基本です。色の変化(緑→黄→赤)もCSS変数と hsl() で実現できます。
<div class="gauge-outer" role="progressbar" aria-valuenow="70" aria-valuemin="0" aria-valuemax="100">
<div class="gauge-inner" style="--hp: 0.7;"></div>
</div>.gauge-outer {
width: 200px;
height: 18px;
background: #222;
border-radius: 9px;
overflow: hidden;
border: 2px solid #444;
}
.gauge-inner {
height: 100%;
/* CSS変数でwidthを決める */
width: calc(var(--hp) * 100%);
/* hpが高いほど緑(120)、低いほど赤(0)に */
background: hsl(calc(var(--hp) * 120), 80%, 45%);
border-radius: 9px;
transition: width 300ms ease, background 300ms ease;
}JavaScriptからは gaugeInner.style.setProperty('--hp', newValue) の1行でゲージが更新されます。aria-valuenow も合わせて更新するとスクリーンリーダー対応になります。
hsl() のHue値を --hp で計算しているのがポイントです。--hp が 1.0(100%)なら Hue は 120(緑)、0.0(0%)なら Hue は 0(赤)になります。色の変化をJavaScriptで条件分岐する必要がなく、値が変われば自動で色が動きます。
ゲージをアニメーションさせるときの注意点は overflow: hidden を外してしまうことです。外すとゲージ内側の角丸が外枠をはみ出して見た目が崩れます。内側の border-radius も設定しておくと、値が小さいときに端が丸くなり自然に見えます。
画面を止めるポップアップ(モーダル)
ゲームオーバー画面やアイテム説明など、プレイを一時停止させるUIはHTMLの <dialog> 要素とCSSアニメーションで作れます。JavaScriptは showModal() と close() の呼び出しだけに絞れます。
<dialog class="game-dialog" id="gameoverDialog">
<div class="dialog-content">
<h2>GAME OVER</h2>
<p>スコア:<span id="finalScore">0</span></p>
<button class="game-btn" id="retryBtn">もう一度</button>
</div>
</dialog>.game-dialog {
border: none;
border-radius: 12px;
padding: 0;
background: transparent;
/* ネイティブのbackdropを活用 */
}
.game-dialog::backdrop {
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(3px);
animation: fadeIn 200ms ease forwards;
}
.dialog-content {
background: #1a1a2e;
color: #eee;
padding: 32px 40px;
border-radius: 12px;
text-align: center;
animation: slideUp 250ms ease forwards;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes slideUp {
from { transform: translateY(30px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}<dialog> 要素の ::backdrop 疑似要素は、モーダルが開いたときに自動で画面全体に重なります。自前で position: fixed の背景要素を用意する必要がなく、z-indexの管理も不要です。backdrop-filter: blur で背景をぼかすとゲーム画面が透けて見えながら操作できない状態が視覚的に伝わります。
閉じるアニメーションはCSSだけでは難しいため、close イベントの直前にクラスを付けてアニメーションを走らせ、完了後に close() を呼ぶ形にするのが現実的です。開くアニメーションはCSSだけで完結します。
古いブラウザでは <dialog> のサポートが不完全な場合があります。ブラウザの対応状況は MDN の互換性テーブルで確認してから採用するかどうか判断してください。
3つのパーツをつなぐCSS変数の設計
ボタン・ゲージ・ポップアップを同じゲームに入れると、色や余白の数値があちこちに散らばりやすくなります。:root にCSS変数としてデザイントークンをまとめておくと、テーマ変更や色調整が1箇所で完結します。
:root {
--color-primary: #3a86ff;
--color-primary-dark: #1a56cc;
--color-bg-dark: #1a1a2e;
--color-text: #eeeeee;
--radius-base: 8px;
--transition-fast: 80ms ease;
--transition-mid: 250ms ease;
}ゲームのジャンルによってボタンの形や色は大きく変わりますが、変数の構造は共通です。ファンタジーRPGなら --color-primary を金色にする、SFゲームなら --color-bg-dark を濃いグレーにするという切り替えが、変数の値を変えるだけで全パーツに反映されます。
CSS変数は var(--name, fallback) の形でフォールバック値を持てます。JavaScriptから渡す値の初期状態が未定義になりうるゲージなど、フォールバックを var(--hp, 1) のように書いておくと、変数がセットされる前に表示が崩れません。
次の行動
この記事で紹介したボタン・ゲージ・ポップアップの3パーツをまず単一のHTMLファイルに組み合わせ、動作を確認してみてください。JavaScriptはゲージの値を変える setProperty とダイアログの showModal() / close() だけに抑えると、CSSとロジックの分離が体験できます。完成したゲームはZIPファイルやHTMLファイルとしてアソバースに投稿して公開できますので、作ったUIを実際のゲームで試す場としてぜひ活用してみてください。
参考資料
- MDN Web Docs — <dialog> 要素
- MDN Web Docs — CSS カスタムプロパティ(変数)
- MDN Web Docs — ::backdrop 疑似要素
- MDN Web Docs — CSS hsl() 関数

