【手を動かす道場】HTML&モダンCSS 10本ノック(箱の物理とFlexbox/Gridによるレイアウト完全制覇)

2026-11-06 / シリーズ「実戦コード・ノック道場」第5話 / HTML, CSS, Flexbox, CSSGrid, フロントエンド, 10本ノック, 初心者向け

プログラミング初心者がWeb制作やフロントエンド開発に足を踏み入れたとき、最も理不尽でイライラさせられるのが**「CSS(Cascading Style Sheets)」**です。

「ボタンを画面の真ん中に置きたいだけなのに、なぜか左端に張り付いて動かない」 「幅を100pxと指定したはずなのに、枠線と余白を足したら親要素を突き破ってレイアウトが崩壊した」 「スマホで見たら文字が重なり、画像が画面外へ吹き飛んでいる」

C言語やPythonのような手続き型言語が得意なエンジニアほど、CSSのこの「思い通りにならなさ」に頭を抱えます。 しかし断言します。CSSは決して「気まぐれな魔法」ではありません。 CSSが思い通りに動かない最大の原因は、ブラウザの描画エンジン(Layout/Reflow)が画面上のピクセルを計算する「ボックスモデルの物理法則」を知らないまま、あてずっぽうにプロパティを書き殴っているからです。

この記事ではエディタの準備もローカルサーバーも不要です。 ブラウザが画面を組み立てる「箱の力学」を5分で理解したら、ページ内に埋め込まれた**「ライブプレビュー道場」で実際にコードを書き換え、リアルタイムに変化する画面を目で確認しながら10本ノックを制覇**してください。


1. ブラウザの描画パイプライン:画面は「箱(Box)」の積み木である

Webブラウザ(ChromeやSafari、Firefoxなど)がHTMLとCSSを受け取ったとき、内部のレンダリングエンジン(BlinkやWebKit)は以下の物理ステップで画面をピクセルへと変換します。

🌐 1. HTML文字列 構文解析 → DOMツリー構築 🎨 2. CSSスタイル 構文解析 → CSSOMツリー構築 3. レンダーツリー結合 DOMツリーとCSSOMツリーを1つに結合する 4. レイアウト計算(Reflow) 各要素の座標・サイズを決定する 5. ペイント&コンポジット GPUでピクセルを描画する

この「4. レイアウト計算(Reflow)」の段階で、ブラウザは画面上にあるすべての要素を「四角い箱(Box)」として計算します。丸いアイコンも、斜めの文字も、ブラウザの目にはすべて「矩形の箱」に見えています。

破壊的な罠:box-sizing: content-box の悲劇

CSSでレイアウトが崩れる原因の半分は、CSSが歴史的経緯から抱えてきた**「箱のサイズの数理モデル」**にあります。

標準設定(content-box)では、プログラマーが width: 200px; と指定したとき、それは「中身の文字の幅」だけを指します。 そこに余白(padding: 20px;)と枠線(border: 2px solid;)を足すと、画面上で実際に占有する幅は以下の足し算になります。

$$\text{実際の幅} = 200\text{px} + (20\text{px} \times 2) + (2\text{px} \times 2) = 244\text{px}$$

content-box(初期設定の罠) 中身の幅(width): 200px + padding: 左右 40px + border: 左右 4px = 全体の占有幅: 244px 親枠を突き破ってレイアウト崩壊! border-box(現代の常識) 指定した幅(width): 200px padding・border は内側に吸収 中身の幅は 156px に自動縮小 = 全体の占有幅: きっちり 200px! 指定どおりに収まる

「幅200pxの箱を作ったつもりが、勝手に244pxに膨張して隣の箱を押し出す」。これがレイアウト崩壊の真犯人です。 これを防ぐため、現代のWeb開発ではすべての要素に対して box-sizing: border-box; を指定し、「paddingもborderも含めて、全体で200pxに収めよ」と物理定義を書き換えるのが世界標準の第一歩です。


2. レイアウトの二大巨頭:Flexbox(1次元)vs CSS Grid(2次元)

かつて、要素を横並びにするには float や inline-block といった不自然なハックが使われ、多くのエンジニアを消耗させました。 しかし現代のCSSには、数学的に洗練された2つの最強レイアウト機構が存在します。

Flexbox(1次元の整列) 親: display: flex 子要素 1 子要素 2 子要素 3 行または列の「単一方向」に沿って 伸縮・整列・余白配分する CSS Grid(2次元の配置) 親: display: grid Header(2列結合) Sidebar Main Footer(2列結合) 縦と横を同時に制御し ページ全体の骨格を一撃で組む

使い分けの基準

  • Flexbox(display: flex): **「1次元(直線状)」**の並び。ナビゲーションバーのメニュー、ボタン内のアイコンと文字、カードの横並びなど、「一方向に向かって要素をどう等間隔に配置するか」「上下左右の中央にどう揃えるか」に特化しています。
  • CSS Grid(display: grid): **「2次元(面・格子状)」**の並び。ダッシュボード画面、フォトギャラリー、ヘッダー・サイドバー・メインコンテンツが組み合わさった「ページ全体の設計図(グリッド)」を一撃で定義するのに最適です。

この2つを正しく組み合わせるだけで、数式のような複雑なパーセント計算を一切書くことなく、スマホから4Kモニターまで美しく伸縮するレスポンシブデザインが完成します。


触って試す:CSS切り替えプレビュー

🎛️ CSS切り替えプレビュー(プロパティ1つで並びが変わる)

コードは書かず、ボタンを押して display / gap / justify-content / 列数を切り替えるだけの演習です。6つの箱の並びと、下に表示されるCSSがどう連動するかを見てください。このあとの道場でコードを書く前に、「プロパティ=並びの物理法則」の対応を目に焼き付けるのが狙いです。

箱 1
箱 2
箱 3
箱 4
箱 5
箱 6

display:
gap:
justify-content(flex用):
列数(grid用):

3. 【実践】ブラウザ内完結・HTML&モダンCSS 10本ノック道場

下のボックスは、入力したHTML/CSSが**リアルタイムに右側のプレビュー画面に描画される「ライブプレビュー道場」**です。 エディタ内のコードを書き換えると、即座にプレビューが更新されます。

課題を読み、コードを修正して**「▶ 採点する」**ボタンを押してください。

MODERN CSS DOJO 全10問・レイアウト完全制覇演習
クリア状況: 0 / 10 問
▼ CSSコードを編集
<!-- 右:リアルタイム描画プレビュー -->
<div>
  <div style="font-size: 0.8rem; color: #94a3b8; margin-bottom: 6px; font-weight: bold;">▼ リアルタイム・プレビュー画面</div>
  <div id="css-preview-container" style="background: #ffffff; border: 2px dashed #475569; border-radius: 8px; min-height: 240px; padding: 12px; overflow: hidden; position: relative;">
    <!-- ライブプレビューがここに差し込まれる -->
  </div>
</div>

function renderTabs() { const tabContainer = document.getElementById(‘css-question-tabs’); tabContainer.innerHTML = ”; questions.forEach((q, idx) => { const btn = document.createElement(‘button’); const isCleared = clearedSet.has(q.id); const isCurrent = idx === currentQIndex; btn.textContent = (isCleared ? ’✅ ’ : ”) + Q${q.id}; btn.style.cssText = padding: 6px 12px; border-radius: 6px; border: 1px solid ${isCurrent ? '#fbbf24' : '#334155'}; background: ${isCurrent ? '#b45309' : (isCleared ? '#064e3b' : '#1e293b')}; color: #f8fafc; font-weight: ${isCurrent ? 'bold' : 'normal'}; cursor: pointer; font-size: 0.85rem; ; btn.onclick = () => selectQuestion(idx); tabContainer.appendChild(btn); }); document.getElementById(‘css-progress-text’).textContent = クリア状況: ${clearedSet.size} / ${questions.length} 問; }

function selectQuestion(idx) { currentQIndex = idx; const q = questions[idx]; document.getElementById(‘css-q-title’).innerHTML = q.title; document.getElementById(‘css-q-desc’).innerHTML = q.desc; document.getElementById(‘css-q-hint’).innerHTML = q.hint; document.getElementById(‘css-editor’).value = q.defaultCss; document.getElementById(‘css-status-box’).style.display = ‘none’; renderTabs(); updateLivePreview(); }

document.getElementById(‘css-editor’).addEventListener(‘input’, updateLivePreview);

document.getElementById(‘css-run-btn’).onclick = () => { updateLivePreview(); const container = document.getElementById(‘css-preview-container’); const q = questions[currentQIndex]; const statusBox = document.getElementById(‘css-status-box’);

try {
  if (q.checker(container)) {
    clearedSet.add(q.id);
    renderTabs();
    statusBox.style.display = 'block';
    statusBox.style.background = '#065f46';
    statusBox.style.color = '#a7f3d0';
    statusBox.innerHTML = `🎉 正解です! Q${q.id} クリア! ${currentQIndex < questions.length - 1 ? '<button id="css-next-btn" style="margin-left:12px; background:#f59e0b; color:#78350f; border:none; padding:4px 12px; border-radius:4px; font-weight:bold; cursor:pointer;">次の問題へ ➔</button>' : '🏆 全10問完全制覇!モダンCSSレイアウトの基礎を完全マスターしました!'}`;
    const nextBtn = document.getElementById('css-next-btn');
    if (nextBtn) nextBtn.onclick = () => selectQuestion(currentQIndex + 1);
  } else {
    statusBox.style.display = 'block';
    statusBox.style.background = '#78350f';
    statusBox.style.color = '#fde68a';
    statusBox.textContent = '⚠️ まだ目標のスタイルが正しく適用されていません。右のプレビュー画面とヒントを確認してください。';
  }
} catch (e) {
  statusBox.style.display = 'block';
  statusBox.style.background = '#7f1d1d';
  statusBox.style.color = '#fecaca';
  statusBox.textContent = '❌ エラー: ' + e.message;
}

};

document.getElementById(‘css-ans-btn’).onclick = () => { document.getElementById(‘css-editor’).value = questions[currentQIndex].answerCss; updateLivePreview(); };

document.getElementById(‘css-reset-btn’).onclick = () => { document.getElementById(‘css-editor’).value = questions[currentQIndex].defaultCss; updateLivePreview(); };

document.getElementById(‘css-editor’).addEventListener(‘keydown’, (e) => { if ((e.ctrlKey || e.metaKey) && e.key === ‘Enter’) { e.preventDefault(); document.getElementById(‘css-run-btn’).click(); } });

selectQuestion(0); })();


4. 全10本ノックの「急所」と模範解答・現場の掟

道場で手を動かしたあとに読むことで、崩れないCSSを書くための「プロの設計原則」を完全インストールします。

第1問〜第3問:Flexboxの3大軸制御(display: flex)

【Q1・Q2】縦横の中央揃え(justify-content と align-items)

.container {
  display: flex;
  justify-content: center; /* 主軸(横方向)の中央揃え */
  align-items: center;     /* 交差軸(縦方向)の中央揃え */
}
  • 物理の急所: CSSの世界で数十年にわたり人類を苦しめてきた「上下中央配置」は、Flexboxの登場によって完全に解決しました。
    • justify-content:アイテムが並ぶメインの方向(デフォルトは横)に沿った配置
    • align-items:メインと垂直に交差する方向(デフォルトは縦)に沿った配置 この2行を書くだけで、どんなサイズの親要素の中であっても、子要素は数学的重心にビシッと静止します。

【Q3】ナビゲーションバーの黄金律(space-between)

.nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
  • マージン計算をしない美学: 左端のロゴと右端のボタンの間に「何ピクセルの余白を空けるか」を計算してはいけません。画面幅がスマホからPCへ広がったとき破綻するからです。 space-between を指定すれば、ブラウザが「親の幅から子要素の幅を引いた残りの余白」をリアルタイムに計算し、自動的に要素の間に均等配分してくれます。

第4問〜第6問:モダンレイアウトの必須技術(gap と CSS Grid)

【Q4】margin を捨てるべき理由(gap プロパティ)

.container {
  display: flex;
  gap: 16px;
}
  • 「最後の要素の余白」問題の撲滅: 従来の margin-right: 16px; による隙間空けは、「一番右端の要素にだけ &:last-child { margin-right: 0; } を書く」という面倒な例外処理が必要でした。 現代の gap は、「要素と要素の間の溝(Gutter)」にしか余白を発生させないため、外側にはみ出す事故が物理的に起きません。

【Q5】レスポンシブ折返し(flex-wrap: wrap)

.container {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
  • スマホ画面での必須プロパティ: Flexboxはデフォルトで「絶対に1行に収めようとして子要素を無理やり縮める(nowrap)」性質があります。タグ一覧や商品カードなど、幅が狭くなったら自然に段落ちさせたい場所には、必ず flex-wrap: wrap; を付与します。

【Q6】CSS Gridの「1fr」という神の単位

.grid-container {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}
  • fr(Fraction:分数・比率)の物理: grid-template-columns: 33.33% 33.33% 33.33%; と書くと、gap の幅が足された瞬間に合計が100%を超えてレイアウトが崩壊します。 1fr は、**「あらかじめ gap を差し引いた後の、利用可能な空きスペースを 1:1:1 で均等山分けせよ」**という知的な相対単位です。パーセント計算の呪縛から完全に解放されます。

第7問〜第10問:実務のUI微調整・崩れ防止テクニック

【Q7】画像の変形を防ぐ(object-fit: cover)

.card-img {
  width: 100px;
  height: 100px;
  object-fit: cover;
}
  • アスペクト比破壊の恐怖: ユーザーが投稿した写真のサイズ(縦長・横長)はバラバラです。CSSで width と height だけを強制固定すると、画像は縦や横に潰れて無惨な見た目になります。 object-fit: cover; を指定すると、**「画像の中心を基準にアスペクト比を100%維持したまま、枠いっぱいにトリミングして綺麗に収める」**ことができます。カードUIやアイコン表示の実務必須テクニックです。

【Q8】箱の物理法則の統一(box-sizing: border-box)

* {
  box-sizing: border-box;
}
  • すべてのWebサイトの1行目に書くべきリセットCSS: 前述の通り、padding や border を足しても全体の指定幅が絶対に膨張しなくなります。これ1行を全体に適用するだけで、CSSの不可解なレイアウト崩れの80%は未然に防がれます。

【Q9】絶対配置(position: relative と absolute)

.parent {
  position: relative; /* 子要素の基準点(アンカー)になる */
}
.badge {
  position: absolute; /* 親の基準点からミリ単位で座標指定 */
  top: 0;
  right: 0;
}
  • 親に relative を忘れると何が起きるか?: position: absolute; を持った要素は、先祖のタグを上にたどり、position(relative等)が指定されている要素を「自分の世界の原点(左上 0, 0)」と見なします。 もし親要素に relative を書き忘れると、原点がブラウザ画面全体の左上(<body>)まで吹っ飛んでいき、通知バッジが遥か遠くの画面の角にポツンと表示されるという怪奇現象が発生します。

【Q10】文字の3点リーダー省略化(CSS三種の神器)

.truncate {
  white-space: nowrap;      /* 1. 絶対に改行させない */
  overflow: hidden;         /* 2. 枠からはみ出た部分を隠す */
  text-overflow: ellipsis;  /* 3. はみ出た末尾を「…」にする */
}
  • 3つ揃わなければ発動しない連立の物理: どれか1つでも欠けると省略記号化は機能しません。「文字を1行に固定し、枠外をマスクし、マスクした境界に … を描画する」。管理画面のユーザー名やファイル名一覧などで最も重宝されるUIテクニックです。

結論:CSSとは「制約の中で自律的に伸縮する流体」である

PhotoshopやFigmaのようなデザインツールで画面を作るとき、キャンバスの幅は「1920px」や「375px」と固定されています。 しかし、現実のWeb空間は違います。 ユーザーが使う画面は、極小のスマートフォン、タブレット、ノートPC、超横長のウルトラワイドモニターまで、千差万別に変化します。

だからこそ、CSSで画面を構築するときに「ピクセル(px)で絶対座標をガチガチに固定する」のは悪手です。

  1. box-sizing: border-box で箱の膨張を抑え込み、
  2. flex や grid で要素に「伸縮するルール(自律神経)」を与え、
  3. gap と fr で余白の計算をブラウザの物理エンジンに委ねる。

この「流体の思想」が指先に宿ったとき、あなたの書くCSSはどんな過酷なデバイス環境でも絶対に崩れない、強靭で美しいWebサイトを生み出すようになるのです。


📝 3行で頭に入る『試験・実務・投資の要点まとめ』

  1. 【ボックスモデルの物理】 初期設定の content-box は余白で外形が勝手に肥大化するため、全体幅に余白を内包させる box-sizing: border-box の適用が現代の絶対常識。
  2. 【FlexboxとGridの使い分け】 1次元の一方向整列・中央揃えは Flexbox(justify/align)、縦横が交差する2次元の碁盤の目レイアウトは CSS Grid(fr単位) を使う。
  3. 【実務の鉄板トリオ】 余白の margin 破綻は gap で解消し、カード画像は object-fit: cover で歪みを防ぎ、長文は white-space/overflow/text-overflow で安全に省略する。

確認クイズ

📝 確認クイズ(3問)