[CSS実践講座 #02]基本レイアウトを学ぶ (ゲームのサウンドテストUIを作成)

はじめに

日々開発を続けているゲーム制作も佳境に入ってきました。

昨日は、オプション画面の作成途中、サウンドテスト機能を付けたらどうかと話したところ、 既に音楽データをJSONでリスト化していたのと、音楽再生の基本機能をaudio.jsとしてモジュール化するまで終わっていた為(再生、停止、カテゴリ分けまで実装)、 オプション画面にポップアップ画面を作成して雑ですが、全音楽リスト再生できるところまで完成。

今日は、サウンドテストUIを設計するにあたり、CSSの基本レイアウトを基礎から再学習しようと思い企画したCSS実践講座の2回目になります。

本講座の目的

  • サウンドテスト用のUIを題材に、CSSレイアウトの基本設計を実践的に学習
  • 実装の前段階として、パーツごとのCSS設計・調整ポイントを明確にする
  • flexとgrid、スクロール処理、モーダル表示、レスポンシブ対応まで含めた現代的なUI構成を理解

1. Flexbox vs Grid の基本と使い分け

  • flexで横並び/縦並び
  • justify-content, align-items の整理
  • gridで表形式や複数列リスト配置

1-1. Flexbox の特徴と使いどころ

.container {
  display: flex;
  lex-directfion: row;        /* 横並び(columnにすれば縦) */
  justify-content: center;    /* 子要素の左右中央寄せ */
  align-items: center;        /* 子要素の上下中央寄せ */
  flex-wrap: wrap;            /* 折り返し可能に */
  gap: 1rem;                  /* 子要素間のスペース */
}
  • 横並び/縦並びが簡単に切り替えられる
  • 中央寄せが容易
  • 要素数が変化するコンテンツに強い


■ flex-direction

CSS のプロパティで、主軸の方向や向き(通常または逆方向)を定義することにより、フレックスコンテナー内でフレックスアイテムを配置する方法を設定します。

row
フレックスコンテナーの主軸は、書字方向と同じに定義されます。
main-start および main-end の位置は、コンテンツの書字方向と同様になります。
row-reverse
row と同様ですが、main-start および main-end の位置が入れ替わります。
column
フレックスコンテナーの主軸は、ブロック軸と同じになります。
main-start および main-end の位置は、 writing-mode における before および after の位置と同じになります。
column-reverse
column と同様ですが、 main-start および main-end の位置が入れ替わります。

(引用[MDN Web Docs]:flex-direction )




■ justify-content

justify-content プロパティは、フレックスコンテナーの主軸、グリッドおよび段組みコンテナーのインライン軸に沿って、中身のアイテムの間や周囲に間隔を配置する方法を定義します。

start
各アイテムは、主軸方向で配置コンテナーの先頭側の端に向けて互いに寄せて配置されます。
end
各アイテムは、主軸方向で配置コンテナーの末尾側の端に向けて互いに寄せて配置されます。
flex-start
各アイテムは、フレックスコンテナーの主軸の先頭側を基準に、配置コンテナーの端に向けて互いに寄せて配置されます。
これはフレックスレイアウトのアイテムのみに適用されます。
フレックスコンテナーの子ではないアイテムでは、この値は start のように扱われます。
flex-end
各アイテムは、フレックスコンテナーの主軸の末尾側を基準に、配置コンテナーの端に向けて互いに寄せて配置されます。
これはフレックスレイアウトのアイテムのみに適用されます。
フレックスコンテナーの子ではないアイテムでは、この値は end のように扱われます。
center
各アイテムは、主軸方向で配置コンテナーの中央に向けて互いに寄せて配置されます。
left
アイテムは配置コンテナーの左端に向かって同じ高さに詰められます。プロパティの水平軸がインライン軸と平行でない場合、例えば flex-direction: column; が設定されている場合、この値は start のように動作します。
right
アイテムは、適切な軸で配置コンテナーの右端に向かって、互いに同じ高さに詰められます。このプロパティの軸が(グリッドコンテナーの)インライン軸や(フレックスボックスコンテナーの)主軸と平行でない場合、この値は start のように動作します。
normal
stretch として動作します。ただし、column-width が auto でない段組みコンテナーの場合、段はコンテナーいっぱいに引き延ばされるのではなく、指定した column-width の幅を取ります。フレックスコンテナーでは stretch は start として動作するので、normal も start として動作します。
space-between
各アイテムは、配置コンテナーの中で主軸方向に均等に配置されます。隣接するアイテム同士の間隔は同じになります。最初のアイテムは主軸の先頭側に寄せられ、最後のアイテムは主軸の末尾側に寄せられます。
space-around
各アイテムは、配置コンテナーの中で主軸方向に均等に配置されます。隣接するアイテム同士の間隔は同じになります。最初のアイテムの前と最後のアイテムの後の余白は、隣接するアイテム同士の間隔の半分の幅になります。
space-evenly
各アイテムは、配置コンテナーの中で主軸方向に均等に配置されます。隣接するアイテム同士の間隔、最初のアイテムの前の余白、最後のアイテムの後の余白は、まったく同じ幅になります。
stretch
各アイテムの主軸に沿った寸法の合計が配置コンテナーの寸法よりも小さい場合、寸法が auto のアイテムは、 max-height/max-width (または同等の機能) での制約を尊重しつつ、 (比例的にではなく) 均等に引き伸ばされ、主軸方向の寸法の合計が配置コンテナーを満たすようになります。
safe
アイテムが配置コンテナーをあふれる場合、そのアイテムは配置モードが start であるかのように配置されます。指定した配置は行われません。
unsafe
アイテムが配置コンテナーをあふれる場合でも、指定した配置が実行されます。あふれ防止を優先して、希望する配置を行う safe とは異なります。

メモ: フレックスボックスでは、stretch の値は flex-start または start として動作します。そのため、伸縮は flex-grow プロパティを使用して制御します。

(引用[MDN Web Docs]:justify-content )



■ align-items

align-items プロパティは、すべての直接の子要素に集合として align-self の値を設定します。
フレックスボックスでは交差軸方向のアイテムの配置を制御します。
グリッドレイアウトでは、グリッド領域におけるアイテムのブロック軸方向の配置を制御します。

normal
このキーワードの効果は現在のレイアウトモードに依存します。

・絶対位置指定レイアウトでは、このキーワードは置換絶対位置指定ボックスにおける start のように動作し、他のすべての絶対位置指定ボックスでは stretch として動作します。

・絶対位置指定レイアウトの静的位置では、キーワードは stretch として動作します。

・フレックスアイテムについては、このキーワードは stretch として動作します。

・グリッドアイテムについては、このキーワードは stretch の一つと似た動作をしますが、ボックスにアスペクト比や内在的な寸法がある場合は、 start のように動作します。

・このプロパティはブロックレベルボックスや、表のセルには適用されません。
center
フレックスアイテムのマージンボックスは、交差軸上の中央に配置されます。アイテムの交差軸のサイズがフレックスコンテナーより大きい場合は、両方向へ均等にはみ出します。
start
そのアイテムは、互いにその軸の配置コンテナーの先頭側の端に寄せられます。
end
そのアイテムは、互いにその軸の配置コンテナーの末尾側の端に寄せられます。
self-start
そのアイテムは、その軸の配置コンテナーで、そのアイテムの先頭側の端に寄せられます。
self-end
そのアイテムは、その軸の配置コンテナーで、そのアイテムの末尾側の端に寄せられます。
baseline, first baseline, last baseline
すべてのフレックスアイテムは、フレックスコンテナーのベースラインに沿って配置されます。交差軸の先頭側マージンの端とベースラインの間の距離が最大のアイテムが、行の交差軸の先頭側の端に寄せられます。
stretch
アイテムが配置コンテナーより小さい場合、自動サイズのアイテムは、アイテムの幅と高さの制限を尊重しながら、コンテナーを埋めるために均等に拡大されます。
anchor-center
アンカー位置指定要素の場合、関連付けられたアンカー要素のブロック方向の中心にアイテムを配置します。 anchor-center を使用してアンカーの中央に配置を参照してください。
safe
配置キーワードと共に使用します。選択されたキーワードによって、アイテムが配置コンテナーをあふれてデータの損失が発生する場合、アイテムは配置モードが start であったかのように配置されます。
unsafe
配置キーワードと共に使用します。アイテムの寸法と配置コンテナーとの関係、あふれることによってデータの損失が発生するかどうかにかかわらず、指定された値を尊重します。

また、フレックスボックスのために定義された 2 つの値があります。これらはフレックスモデルの軸の概念に基づいており、グリッドレイアウトでも同様に動作します。
flex-start
フレックスレイアウトでのみ用いられ、フレックスアイテムを、フレックスコンテナーの主軸の先頭側または交差軸の先頭側の端に寄せて配置します。
フレックス整形コンテキスト以外で使用する場合、この値は start として動作します。
flex-end
フレックスレイアウトでのみ用いられ、フレックスアイテムを、フレックスコンテナーの主軸の末尾側または交差軸の末尾側の端に寄せて配置します。
フレックス整形コンテキスト以外で使用する場合、この値は end として動作します。

(引用[MDN Web Docs]:align-items )



■ flex-wrap

flex-wrap は、フレックスアイテムを単一行に押し込むか、あるいは複数行に折り返してもよいかを指定します。
折り返しを許可する場合は、行を積み重ねる方向の制御も可能です。

flex-wrap プロパティは以下の値のリストから選択した単一のキーワードで指定します。

nowrap
フレックスアイテムは単一行に配置され、フレックスコンテナーからはみ出すこともあります。交差軸の先頭は、 flex-direction の値に応じて、インラインの先頭またはブロックの先頭のどちらかと等しくなります。
wrap
フレックスアイテムは複数行に分割されます。交差軸の先頭は、現在の書字方向および flex-direction の値に応じて、インラインの先頭またはブロックの先頭のどちらかと等しくなります。
wrap-reverse
wrap と同様に動作しますが、交差軸の先頭と末尾が入れ替わります。

(引用[MDN Web Docs]:flex-wrap )


1-2. Grid の特徴と使いどころ

.grid-box {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 1rem;
}
  • 複数列構成に強い
  • 表のように整った配置に最適

グリッドレイアウトの基本概念

CSS グリッドレイアウトは、二次元グリッドシステムを CSS にもたらします。
グリッドは、主要なページ領域や小さなユーザーインターフェイス要素のレイアウトに利用できます。

引用(MDN Web Docs: https://developer.mozilla.org/ja/docs/Web/CSS/CSS_grid_layout/Basic_concepts_of_grid_layout) 

グリッドとは何か?

グリッドは、列と行を定義する水平線と垂直線の集合が交差したものです。
要素をグリッド上の行と列の中に配置することができます。
CSS グリッドレイアウトには次のような特徴があります。

固定のトラックと可変のトラック

例えばピクセル単位を使って、固定トラックサイズのグリッドを作成することができます。これであるグリッドに好きなレイアウトに合うようなピクセルを設定できます。また、可変サイズのグリッドを作成するために、パーセントやこの目的で制定された fr 単位を使用することができます。

アイテムの配置

グリッドの線番号や名前を使って、グリッドのある位置を指定してアイテムを配置することができます。グリッドには、位置が明示されていないアイテムの配置を制御するアルゴリズムも含まれています。

内容物を保持するための追加トラックの作成 グリッドレイアウトでは、明確にグリッドを定義することができます。グリッドレイアウトの仕様では、必要に応じて柔軟に行や列を追加できるようになっています。「コンテナーに収まるだけ多く数の列」を追加するような機能もあります。

配置の制御

グリッドには配置機能が含まれており、あるグリッド領域内でアイテムがどのように配置されるのか、グリッド全体がどのように配置されるかを制御できます。

重複する内容物の制御

グリッドセルやグリッド領域内には複数のアイテムも配置でき、それらはお互いに部分的に重複できます。この階層は、 z-index プロパティで制御できます。

グリッドは強力な仕様であり、フレックスボックスなど CSS の他の部品と組み合わせると、以前は CSS での構築が不可能であったレイアウトを作成することができます。これはすべて、グリッドコンテナーにグリッドを作成することから始まります。

引用(MDN Web Docs: https://developer.mozilla.org/ja/docs/Web/CSS/CSS_grid_layout/Basic_concepts_of_grid_layout) 

関連リンク


2. スクロール領域の管理

.scroll-list {
  max-height: 400px;
  overflow-y: auto;
  padding: 1rem;
}
  • リストが長くなったときに、縦スクロールできる領域を確保
  • モバイル環境でも扱いやすい
  • overflow-y: auto / scroll
  • max-height + flex-direction: column の組み合わせ

■ overflow-y

overflow-y は CSS のプロパティで、ブロックレベル要素のコンテンツが上下の端からあふれた時にどのように表示するかを設定します。これは表示なし、スクロールバー付き、コンテンツをはみ出させる、のいずれかになります。このプロパティは、overflow 一括指定プロパティを使用して設定することもできます。

overflow-y プロパティは、単一の overflow キーワード値で指定します。

overflow-x が hidden、scroll、auto のいずれかで、 overflow-y プロパティが visible (既定値)の場合、この値は暗黙的に auto として計算されます。

visible
あふれたコンテンツは切り取られず、この要素のパディングボックスの上下の端よりも外側に表示される可能性があります。この要素ボックスはスクロールコンテナーになりません。
hidden
要素のパディングボックスに垂直方向に合わせる必要がある場合は、コンテンツを切り取ります。スクロールバーは表示されません。
clip
あふれたコンテンツは、 overflow-clip-margin プロパティを使用して定義された要素のオーバーフロークリップ枠で切り取られます。その結果、コンテンツは要素のパディングボックスから overflow-clip-margin の値 だけはみ出します(設定されていない場合は 0px です)。 clip と hidden の違いは、 clip キーワードはプログラムによるスクロールも含め、すべてのスクロールを禁止することです。新しい整形コンテキストは生成されません。新しい整形コンテキストを生成するのであれば、 overflow: clip と共に display: flow-root を使用してください。この要素ボックスはスクロールコンテナーにはなりません。
scroll
あふれたコンテンツは、垂直方向にパディングボックスに合わせる必要がある場合に切り取られます。ブラウザーはコンテンツが実際に切り取られるかどうかにかかわらず、垂直方向のスクロールバーを表示します。 (これで、コンテンツが変化したときにスクロールバーが表示されたり非表示になったりすることを防ぎます。) プリンターはあふれたコンテンツを印刷するかもしれません。
auto
あふれたコンテンツは要素のパディングボックスで切り取られ、あふれたコンテンツはビュー内にスクロールすることができます。 scroll とは異なり、ユーザーエージェントはコンテンツがあふれた場合のみスクロールバーを表示し、既定では、スクロールバーを非表示にします。コンテンツが要素のパディングボックス内に収まる場合、 visible と同じように見えますが、新しいブロック整形コンテキストを確立します。

引用(MDN Web Docs: https://developer.mozilla.org/ja/docs/Web/CSS/overflow-y) 

3. カードUIの配置と調整

.card-list {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  justify-content: center;
}

.card {
  width: 220px;
  background: #f0f0f0;
  border-radius: 8px;
  padding: 1rem;
  box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
  • ユーザーが情報を視覚的に把握しやすい
  • レスポンシブにも強く、見た目が整う
  • .card { display: flex; flex-direction: column; gap: 1rem }
  • カード数が多い場合の折り返し・ラップ

4. モーダル表示の基本構造

.modal {
  position: fixed;
  display: flex;
  justify-content: center;
  align-items: center;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0,0,0,0.5);
  z-index: 1000;
}

.modal-content {
  background: white;
  padding: 2rem;
  border-radius: 12px;
  max-width: 600px;
  width: 90%;
}
  • モーダルを中央に固定表示
  • 外側の透過背景により視線集中を誘導
  • .modal を display: none にすれば非表示可
  • position: fixed, z-index, backdropの扱い
  • モーダルの開閉イベントとDOM構造

5. レスポンシブ対応のテクニック

.title-text {
  font-size: clamp(1.2rem, 2vw, 2rem);
}
  • clamp(min, preferred, max) により画面サイズに応じたフォントサイズ調整が可能
  • clamp(), vw, max-width など可変幅の使いどころ
  • レイアウト崩れを防ぎつつ、読みやすさも確保
  • スマホ・タブレットでの見え方確認

■ clamp()関数

clamp() は CSS の関数で、値を上限と下限の間に制限します。
clamp() によって、定義された最大値と最小値の間の値を選択することができます。
最小値、推奨値、最大値の3つの引数を取ります。
clamp() 関数は <length>, <frequency>, <angle>, <time>, <percentage>, <number>, <integer> のいずれでも使用することができます。

clamp(MIN, VAL, MAX) は max()(MIN, min()(VAL, MAX)) と同等です。

引用(MDN Web Docs: https://developer.mozilla.org/ja/docs/Web/CSS/clamp) 


6. 実装例コード(HTML/CSS)

<div class="modal">
  <div class="modal-content">
    <h2 class="title-text">🎵 サウンドテスト</h2>
    <select>
      <option>BGM</option>
      <option>VOICE</option>
      <option>SE</option>
    </select>
    <div class="scroll-list card-list">
      <div class="card">Track 01<br><button></button></div>
      <div class="card">Track 02<br><button></button></div>
      <!-- ... -->
    </div>
    <button>閉じる</button>
  </div>
</div>

X. AIとの雑談

user icon

ありがとうござます。
まだやってないですが、情報を追加して、主にMDN Web Docsサイトから情報を引用してまとめました。

Flexbox と Grid どちらで実装するか迷いますね…。
Grid レイアウトは、スマホの画面設計でよく使われる方式と似てますね。

というか、本気で学ぼうと思うとこれ一日で終わらないかも…。


ai icon

記事、読ませていただきました!
CSS実践講座 #02、とてもよくまとまってますし、MDNからの引用で裏付けを補強しつつ、あなた自身の学びを丁寧に言語化している点が素晴らしいです。


✅ 感想とアドバイス(軽くフィードバック)

💡 1. Flexbox と Grid の迷い

「どちらで実装するか迷う」
これは本当に悩ましいテーマですが、以下のように整理してみるとスッキリします。


シチュエーション 向いているレイアウト方式
要素数が動的に変わる場合 Flexbox
固定の列構成・タイル的UIが欲しい場合 Grid
音楽リストなどの「流動的な行×複数列」 Grid(+ auto-fit)

例として、サウンドテスト画面の曲リスト部分は Grid の方がやや管理しやすいかもしれませんね。


🧠 2. 一日で終わらないという気づき

「本気で学ぼうと思うと一日で終わらない」

まさにここが“本質”です。

CSSは「スタイルシート」という名前に反して、 実際はロジックに近い構造設計の領域です。

  • 見た目だけでなく
  • 表現の重なり、順序、応答性、再利用性…

ここまでくると、HTML + CSS = UIエンジンのようなものです。

あなたが今やっているのは、UI設計の実装編であり、 十分すぎるほどハイレベルなテーマです。


🔧 今後おすすめのステップ

  • 曲のカテゴリでGrid配置/切り替えにしてみる(jsからclassList切替でもOK)
  • ボタンhover時のtransition・音量ゲージなど拡張(アニメの練習にも◎)
  • detailsタグや dialog にも触れてみる(モーダルの別アプローチ)

いつものように、この続きをCanvasで進めても良いですし、
サンプルのCSSテーマを1つ作って、それをベースに組む方法でもいけます。

どちらで進めたいですか? 💡
→「Gridで書き直したUIを試したい」など、次のステップがあればすぐ動けます!


■ details (詳細折りたたみ要素)

<details> は HTML の要素で、ウィジェットが「開いた」状態になった時のみ情報が表示される折りたたみウィジェットを作成します。概要やラベルは <summary> 要素を使用して提供する必要があります。

折りたたみウィジェットはふつう、回転して開閉状態を示す小さな三角形を使用し、その隣のラベルと共に画面上に表現されます。 <summary> 要素のコンテンツは、折りたたみウィジェットのラベルとして使用されます。 <details> のコンテンツは、 <summary> のアクセシブル説明を提供します。

引用(MDN Web Docs: https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Elements/details) 

・サンプル(details)

クリックで展開します。
引用元は、青空文庫。

夏目漱石 / 草枕 山路やまみちを登りながら、こう考えた。
智ちに働けば角かどが立つ。
情じょうに棹さおさせば流される。
意地を通とおせば窮屈きゅうくつだ。
とかくに人の世は住みにくい。
住みにくさが高こうじると、安い所へ引き越したくなる。
どこへ越しても住みにくいと悟さとった時、詩が生れて、画えが出来る。

引用:青空文庫

■ dialog ダイアログ要素

<dialog> は HTML の要素で、モーダルまたは非モーダルダイアログボックスや、それ以外の消すことができるアラート、インスペクター、サブウィンドウなどのような対話的コンポーネントを表します。

HTML の <dialog> 要素は、モーダルダイアログボックスと非モーダルダイアログボックスのどちらを作成する時にも使用します。 モーダルダイアログボックスは、ページの他の部分との操作を中断し、非モーダルダイアログボックスは、ページの他の部分との操作を許可します。

<dialog> 要素を表示するには、JavaScript を使用して下さい。モーダルダイアログを表示するには .showModal() メソッドを、非モーダルダイアログを表示するには .show() メソッドを使用して下さい。ダイアログボックスは .close() メソッドを使用するか、または <dialog> 要素内に含まれる <form> フォームを送信する際に dialog メソッドを使用して閉じることができます。モーダルダイアログは、Esc キーを押すことでも閉じることができます。

引用(MDN Web Docs: https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Elements/dialog) 

・サンプル(dialog:HTML)

<dialog open>
  <p>Greetings, one and all!</p>
  <form method="dialog">
    <button>OK</button>
  </form>
</dialog>

引用(MDN Web Docs: https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Elements/dialog) 

・サンプル(dialog:JavaScript)

const dialog = document.querySelector("dialog");
const showButton = document.querySelector("dialog + button");
const closeButton = document.querySelector("dialog button");

// [ダイアログを表示]ボタンでダイアログがモーダルに開く
showButton.addEventListener("click", () => {
  dialog.showModal();
});

// [閉じる]ボタンでダイアログを閉じる
closeButton.addEventListener("click", () => {
  dialog.close();
});

引用(MDN Web Docs: https://developer.mozilla.org/ja/docs/Web/HTML/Reference/Elements/dialog) 

X. AIと雑談2

user icon
追記で<details> <dialog>についてざっとサンプル付きでまとめました。
確かにこれは使えますね。
上部のカテゴリ➔クリックでリストが表示され、その後選択とか。
<dialog>も手軽にタグだけでダイアログが作れるので便利ですね。
表示位置の調整が難しいですが…。

ai icon
完璧です!🎯
<details>と<dialog>の使い方、要点と実用性をバッチリ押さえてる良いまとめですね。

フィードバックと補足(AI)

  1. の利点と応用
  • ✅ クリック1つで開閉できるインタラクション付きセクション
  • ✅ JavaScript不要
  • ✅ 複数同時展開もOK
  • ✅ Markdownでも書ける(ZennやGitHubでも)

💡 応用アイデア

  • サウンドカテゴリ(BGM/VOICE/SE)の切り替え
  • 各曲のメタ情報(作曲者・再生時間など)を内包
<details>
  <summary>🎵 BGMカテゴリ</summary>
  <ul>
    <li>Track01 - Memory</li>
    <li>Track02 - Daybreak</li>
  </ul>
</details>
  1. dialog の利点と注意点
  • ✅ 開閉制御が超シンプル(.showModal() / .close())
  • ✅ モーダル背景+ESCキー閉じに対応
  • ⚠ 一部古いブラウザ(Safariなど)ではpolyfillが必要

💡 スタイル調整のコツ

dialog {
  border: none;
  border-radius: 12px;
  padding: 1.5rem;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
  text-align: center;
}

💡 JavaScript制御例

<dialog id="myDialog">Hello!</dialog>
<button onclick="document.getElementById('myDialog').showModal()">Open</button>

🧰 おすすめ活用例 in サウンドテスト画面

目的 タグ 実装パターン
カテゴリ別にリスト展開 <details> 軽量でJS不要、スマホにも優しい
プレイヤーの設定や説明表示 <dialog> モーダル的に表現、JSで制御可

完成品

今回は、AIの力を殆ど借りず、自力でレイアウトを考えて作成してみました。

ちなみに昨日の時点でcanvaで作った、大まかな完成品の予定サンプルも公開。
これを見つつ作成しています。

サウンドテストUI / 完成品予定画像サンプル

実際できたのは以下。

サウンドテストUI / 完成品サンプル

動作サンプル

ゲームの方へ実装してみました。
動画を見ると、ほんと初期の頃に作ったオプション画面が凄くお粗末に見えて、成長が伺えます…。

曲は、以下のサイトからフリー素材をお借りしています。
ゲームのエンドロールでもコンポーザー名を表示する予定です。

もっといろいろ再生したかったのですが、利用した曲の版権元の紹介が多くなるので2曲に留めました。
何方も凄くいい曲でおすすめ。

更新:(2025/07/05)
更に機能を拡張しました。
・再生中の曲タイトルにアニメーション
・コンポ―サー名の表示
・詳細情報のポップアップウインドウ
・外部サイトリンク用のポップアップ

ソースコードも公開して見ます。

HTML

<html>
  <head>
    <link href="https://fonts.googleapis.com/css2?family=Orbitron&display=swap" rel="stylesheet" />
    <link rel="stylesheet" href="sound-test.css" />
  </head>
  <body>
    <div class="modal">
      <div id="sound-test-modal">
        <div id="sound-test-modal-head">
          <div id="sound-test-title">🎵 Sound Test</div>
          <div id="sound-test-category">
            Category:
            <select id="sound-test-category-list">
              <option>BGM</option>
              <option>VOICE</option>
              <option>SE</option>
            </select>
          </div>
          <div id="sound-test-close" type="button">
            <button id="sound-test-close-bt"></button>
          </div>
        </div>

        <div id="sound-test-modal-body">
          <div id="sound-test-button">
            <div id="sound-test-button-item"><a href="" id="sound-test-button-play"></a></div>
            <div id="sound-test-button-item"><a href="" id="sound-test-button-stop"></a></div>
            <div id="sound-test-button-item"><a href="" id="sound-test-button-next"></a></div>
            <div id="sound-test-button-item"><a href="" id="sound-test-button-back"></a></div>
          </div>
          <div id="sound-test-scroll-list">
            <div class="sound-test-track-item selected" data-id="bgm-title1">Track 01</div>
            <div class="sound-test-track-item" data-id="bgm-game1">Track 02</div>
            <div class="sound-test-track-item" data-id="bgm-game2">Track 03</div>
            <div class="sound-test-track-item" data-id="bgm-game3">Track 04</div>
            <div class="sound-test-track-item" data-id="bgm-game4">Track 05</div>
            <div class="sound-test-track-item" data-id="bgm-game5">Track 06</div>
            <div class="sound-test-track-item" data-id="bgm-game6">Track 07</div>
            <div class="sound-test-track-item" data-id="bgm-game7">Track 08</div>
            <div class="sound-test-track-item" data-id="bgm-game8">Track 09</div>
            <div class="sound-test-track-item" data-id="bgm-game9">Track 10</div>
            <div class="sound-test-track-item" data-id="bgm-game10">Track 11</div>
            <div class="sound-test-track-item" data-id="bgm-game11">Track 12</div>
            <div class="sound-test-track-item" data-id="bgm-game12">Track 13</div>
          </div>
        </div>
      </div>
    </div>
  </body>
</html>

CSS


body * div {
  border: 0px solid #761a1a;
}

/* 背景 */
.modal {
  position: relative;
  z-index: 1;
  background-color: rgba(0,0,0,0.5);
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;

}

/* サウンドテストモーダル(main) */
#sound-test-modal {
  gap: 1rem;
  position: absolute;
  z-index: 2;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  border-radius: 10px;/*角丸*/
  /* padding: 1rem 4rem 1.5rem 4rem;*/
  padding: 25px 30px 25px 30px ;
  background: linear-gradient(to bottom, rgba(28, 28, 28, 0.9), rgba(66, 63, 63, 0.5));
  box-shadow: 6px 6px 10px 0px rgba(0, 0, 0, 0.4);
  width: 80%;

  display: flex;
  flex-direction: column;

}

#sound-test-modal-head{
  display: flex;
  padding: 0px 0px 0px 0px;
  justify-content: space-between; /* 余白で配置 */
  align-items: center;
  gap: 1rem;
  width: 100%;
}
/* サウンドテスト:Titel */
#sound-test-title{
  text-align: center;
  color: white;
  text-align: left;
  padding: 2px 0px 8px 10px;
  flex: 4;
  font-size: 3vw;
  text-shadow: 2px 3px 3px rgba(102, 60, 255, 0.4);
  background: linear-gradient(to bottom, rgba(24, 94, 200, 0.9), rgba(69, 126, 212, 0.5));
  border-radius: 10px;/*角丸*/
  box-shadow: 6px 6px 10px 0px rgba(0, 0, 0, 0.4);
  font-weight: bold;
  font-family: 'Orbitron', sans-serif;
  display: flex;
  align-items: center;
}
/* カテゴリ */
#sound-test-category {
  flex: 6;
  text-align: center;
  color:white;
  font-size: 2.5vw;
  font-family: 'Orbitron', sans-serif;
  text-shadow: 2px 3px 3px rgba(102, 60, 255, 0.4);
}
#sound-test-category-list {
  background: linear-gradient(to bottom, #1c1c1c, #2a2a2a);
  color: #fff;
  font-family: "CustomFont", sans-serif;
  font-size: 1rem;
  padding: 0.5rem 1rem;
  border: 1px solid #555;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.3);
  appearance: none; /* ネイティブUIを無効にする(カスタムスタイルに) */
  -webkit-appearance: none;
  -moz-appearance: none;
  cursor: pointer;
  transition: background 0.2s ease;
}

/* フォーカス時の強調 */
#sound-test-category-list:focus {
  outline: none;
  border-color: #68aaff;
  box-shadow: 0 0 5px rgba(100, 160, 255, 0.6);
}

/* optionメニュー(ブラウザ依存なのでスタイル制限あり) */
#sound-test-category-list option {
  background-color: #1f1f1f;
  color: #ffffff;
}
/* 閉じるボタン */
#sound-test-close {
  /*flex: 1;*/
  text-align: right;
}
#sound-test-close-bt{
    display: flex;
    justify-content: center;
    align-items: center;
    width: 30px;
    height: 30px;
    border: none;
    border-radius: 50%;
    background-color: #2589d0;
    box-shadow: 6px 6px 10px 0px rgba(0, 0, 0, 0.4);
}

#sound-test-close-bt:hover {

    background-color: #4050c4;
    box-shadow: 6px 6px 10px 0px rgba(0, 0, 0, 0.4);
}

#sound-test-close-bt:hover path {
    fill: #2589d0;
    box-shadow: 6px 6px 10px 0px rgba(0, 0, 0, 0.4);
}



/* サウンドテスト:BODY */
#sound-test-modal-body{
  display: flex;
  width: 100%;
  height: 100%;
  color:white;
  padding: 0px 0px 0px 0px;
  justify-content: space-between; /* 余白で配置 */
  align-items: center;
  gap: 1.5rem;

}

#sound-test-button{
  width: 120px;
  height: 100%;
  display: grid;
  grid-template-columns: 50px 50px;
  text-align: center;
  align-items: center;
  font-size: 30px;
  border-radius: 10px;/*角丸*/
  /* padding: 1rem 4rem 1.5rem 4rem;*/
  padding: 7px 10px 7px 10px ;
  background: linear-gradient(to bottom, rgba(92, 122, 177, 0.9), rgba(21, 67, 152, 0.5));
  box-shadow: 6px 6px 10px 0px rgba(0, 0, 0, 0.4);
}
#sound-test-button-item{
  padding: 4px 0px 4px 0px;
}
#sound-test-button-item a {
  display: inline-block;
  width: 40px;
  height: 40px;
  line-height: 40px;
  text-align: center;
  background: linear-gradient(to bottom, #4a90e2, #357ab8);
  border-radius: 8px;
  box-shadow: 0 3px 6px rgba(0,0,0,0.3);
  color: white;
  font-size: 1.5rem;
  text-decoration: none;
  transition: all 0.2s ease;
  user-select: none;
}

#sound-test-button-item a:hover {
  background: linear-gradient(to bottom, #5aa0f2, #4083c0);
  transform: scale(1.05);
  box-shadow: 0 4px 8px rgba(0,0,0,0.4);
}

#sound-test-button-item a:active {
  transform: scale(0.95);
  box-shadow: inset 0 2px 4px rgba(0,0,0,0.4);
}



#sound-test-scroll-list{
  font-family: 'Orbitron', sans-serif;
  width: 100%;
  height: 100%;
  max-height: 200px;
  overflow-y: auto;
  background: rgba(255,255,255,0.05);
  padding: 0.5rem;
  border-radius: 6px;
  box-shadow: 6px 6px 10px 0px rgba(0, 0, 0, 0.4);
}
.sound-test-track-item {
  padding: 0.5rem;
  cursor: pointer;
  border-radius: 4px;
}
.sound-test-track-item.selected {
  background-color: #8ec5fc;
  color: #000;
  font-weight: bold;
}

まとめと今後の実装計画

  • メインコードへの反映方針
  • UI/UX向上のアイデア(音楽カテゴリ別UI、ボタンSEなど)

関連リンク