読みやすい本文レイアウト
本文をmax-width: 65chの読みやすい行長に収め、写真や図版だけをグリッドで全幅にはみ出させるブレイクアウト構成の記事レイアウト。読了プログレスバー付き。
ライブデモ
コード
HTML
<!-- 読みやすい本文レイアウト:本文は65ch幅、図版だけ全幅にはみ出すブレイクアウト構成。表示幅トグルで measure の効きを実演 -->
<div class="pm" id="pmRoot">
<div class="pm__progress" id="pmProgress"><span></span></div>
<div class="pm__toolbar">
<span class="pm__toolbar-label">表示幅</span>
<div class="pm__btns">
<button type="button" data-w="pc" class="is-active">PC</button>
<button type="button" data-w="tablet">タブレット</button>
<button type="button" data-w="mobile">スマホ</button>
</div>
</div>
<div class="pm__frame" id="pmFrame">
<header class="pm__masthead">
<span class="pm__kicker">DESIGN NOTES</span>
<h1>読みやすさは、行の長さで決まる</h1>
<p class="pm__meta">編集部 ・ 2026年7月 ・ 読了目安 4分</p>
</header>
<article class="pm__article">
<p class="pm__lead">
本文の読みやすさは、フォントサイズや行間だけでなく「1行の文字数(measure)」に大きく左右されます。
長すぎる行は視線が迷子になり、短すぎる行は改行が多く落ち着きません。
</p>
<p>
欧文タイポグラフィの経験則では、1行あたり45〜75文字(日本語ではおよそ35〜40字)が読みやすいとされています。
CSSでは <code>max-width: 65ch</code> のように <code>ch</code> 単位を使うことで、
フォントサイズに応じて自然に測りやすい行長を保てます。
上の「表示幅」を切り替えると、画面が広がっても本文だけは読みやすい幅に留まるのが分かります。
</p>
<h2>図版だけは、はみ出させる</h2>
<p>
本文は読みやすい幅に収めたい一方で、写真や図版は画面いっぱいに見せたいことがあります。
その場合は本文コンテナをグリッドにして、通常の要素は中央カラムに、図版だけ全幅のカラムへ配置する
「ブレイクアウト(breakout)」というテクニックが使えます。
</p>
<figure class="pm__breakout">
<img src="https://picsum.photos/seed/prosemeasure01/1600/700" alt="" loading="lazy">
<figcaption>本文の幅を超えて全幅に広がる図版。視覚的なリズムの区切りにもなる。</figcaption>
</figure>
<p>
実装は <code>grid-template-columns</code> で
「余白 / 本文幅 / 余白」の3カラムを定義し、通常の子要素は中央カラムに配置、
図版だけ <code>grid-column: 1 / -1</code> で全カラムを貫通させるだけです。
</p>
<blockquote class="pm__pullquote">
「余白の設計は、何を読ませ、何を見せるかの意思表示である。」
</blockquote>
<p>
この構成なら、記事内に何度図版を挟んでも本文の読みやすさを損なわず、
かつ図版の見せ場だけはしっかりと画面を使い切ることができます。
レスポンシブ対応も <code>min()</code> 関数ひとつで済み、追加のメディアクエリはほぼ不要です。
</p>
</article>
</div>
</div>
CSS
/* 全体:本文は65ch幅の読みやすいレイアウト、図版だけ全幅にブレイクアウトする記事構成 */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Hiragino Sans", "Yu Gothic", system-ui, sans-serif;
background: #faf9f6;
color: #262320;
min-height: 100vh;
display: block; /* ビルダーの flex 中央寄せを解除し、記事を自然な縦積みで表示 */
}
/* ルート:width:100% が無いと親の flex 中央寄せで潰れるため必須 */
.pm { width: 100%; padding: 0 0 80px; position: relative; }
/* 上部の読了プログレスバー(幅は JS がスクロール量に応じて更新) */
.pm__progress { position: fixed; top: 0; left: 0; width: 100%; height: 3px; background: transparent; z-index: 10; pointer-events: none; } /* sticky ツールバー上端をクリック遮断しないよう pointer-events:none */
.pm__progress span { display: block; height: 100%; width: 0%; background: #b4541f; }
/* 表示幅トグル:ボタンでフレーム幅を変え、65ch measure とブレイクアウトの効きを実演 */
.pm__toolbar {
position: sticky; top: 0; z-index: 6;
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
padding: 14px 20px; margin-bottom: 4px;
background: rgba(250, 249, 246, .92); backdrop-filter: blur(6px);
border-bottom: 1px solid #ece6d7;
}
.pm__toolbar-label { font-size: 11px; font-weight: 800; letter-spacing: .12em; color: #8a8478; }
.pm__btns { display: flex; gap: 6px; }
.pm__btns button {
font-size: 12.5px; font-weight: 700; padding: 6px 14px; border-radius: 999px;
border: 1px solid #e0d8c6; background: #fff; color: #6b6456; cursor: pointer;
transition: background .15s, color .15s, border-color .15s;
}
.pm__btns button:hover { background: #f3ece0; }
.pm__btns button.is-active { background: #b4541f; border-color: #b4541f; color: #fff; }
/* 表示幅フレーム:ここに幅トグルが効く。中の本文は 65ch を保ち、図版だけ全幅にはみ出す */
.pm__frame {
width: var(--pm-w, 100%);
max-width: 100%;
margin: 0 auto;
padding: clamp(20px, 5vw, 40px) 0 0;
transition: width .35s ease;
}
.pm__masthead { text-align: center; padding: 0 20px 32px; }
.pm__kicker { display: inline-block; font-size: 11px; font-weight: 800; letter-spacing: .28em; color: #b4541f; margin-bottom: 10px; }
.pm__masthead h1 { font-size: clamp(24px, 4.4vw, 36px); line-height: 1.4; font-weight: 800; max-width: 20ch; margin: 0 auto 10px; }
.pm__meta { font-size: 12.5px; color: #8a8478; }
/* ブレイクアウト・グリッド:余白 / 本文幅(65ch) / 余白 */
.pm__article {
display: grid;
grid-template-columns: minmax(20px, 1fr) min(65ch, 100% - 40px) minmax(20px, 1fr);
}
.pm__article > * { grid-column: 2; }
.pm__article > .pm__breakout { grid-column: 1 / -1; }
.pm__lead { font-size: 17px; line-height: 2; font-weight: 700; margin-bottom: 20px; }
.pm__article p { font-size: 15.5px; line-height: 2; margin-bottom: 18px; color: #35312b; }
.pm__article code {
font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
font-size: 13px; background: #efe9dd; color: #8a4a1c;
padding: 2px 6px; border-radius: 5px;
}
.pm__article h2 {
font-size: clamp(19px, 2.8vw, 24px); font-weight: 800;
margin: 30px 0 14px; padding-top: 16px; border-top: 1px solid #e6e0d2;
}
/* 全幅の図版(ブレイクアウト) */
.pm__breakout { width: 100%; margin: 30px 0; }
.pm__breakout img {
width: 100%; aspect-ratio: 1600 / 700; max-height: 56vh; object-fit: cover;
display: block; border-radius: 4px; background: #ece6d7; /* 読み込み前のプレースホルダ色(CLS対策) */
}
.pm__breakout figcaption {
width: min(65ch, 100% - 40px); margin: 10px auto 0;
font-size: 12.5px; color: #8a8478; text-align: center;
}
/* 引用 */
.pm__pullquote {
font-size: clamp(18px, 2.4vw, 22px); font-weight: 700; line-height: 1.7;
color: #b4541f; padding: 4px 0 4px 20px; margin: 26px 0;
border-left: 3px solid #b4541f;
}
@media (prefers-reduced-motion: reduce) {
* { scroll-behavior: auto; }
.pm__frame, .pm__btns button { transition: none; }
}
JavaScript
// (A) 表示幅トグル:フレーム幅を変え、65ch の measure とブレイクアウトの効きを実演(null安全)
(() => {
const root = document.getElementById('pmRoot');
const frame = document.getElementById('pmFrame');
if (root && frame) {
// 2カラム一覧のカード(フレーム幅 約475px)でも3段が見分けられるよう、tablet/mobile はそれより狭くする。
// pc=100% は詳細モーダル(〜900px幅)で 65ch に余白が付き、measure の効きがはっきり分かる。
const widths = { pc: '100%', tablet: '380px', mobile: '290px' };
const buttons = root.querySelectorAll('[data-w]');
buttons.forEach((btn) => {
btn.addEventListener('click', () => {
const w = widths[btn.getAttribute('data-w')] || '100%';
frame.style.setProperty('--pm-w', w);
buttons.forEach((b) => b.classList.remove('is-active'));
btn.classList.add('is-active');
// 幅変更で記事高さが変わるため、プログレスバーを即再計算(別IIFEのresizeハンドラを起こす)
window.dispatchEvent(new Event('resize'));
});
});
}
})();
// (B) スクロール位置に応じて上部の読了プログレスバーの幅を更新(null安全)
(() => {
const bar = document.getElementById('pmProgress');
const article = document.querySelector('.pm__article');
const fill = bar ? bar.querySelector('span') : null;
if (!bar || !article || !fill) return;
const update = () => {
const rect = article.getBoundingClientRect();
const total = rect.height - window.innerHeight;
const scrolled = -rect.top;
const pct = total > 0 ? Math.min(100, Math.max(0, (scrolled / total) * 100)) : 0;
fill.style.width = pct + '%';
};
window.addEventListener('scroll', update, { passive: true });
window.addEventListener('resize', update);
update();
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「読みやすい本文レイアウト」の効果を追加してください。
# 追加してほしい効果
読みやすい本文レイアウト(レイアウト & グリッド)
本文をmax-width: 65chの読みやすい行長に収め、写真や図版だけをグリッドで全幅にはみ出させるブレイクアウト構成の記事レイアウト。読了プログレスバー付き。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 読みやすい本文レイアウト:本文は65ch幅、図版だけ全幅にはみ出すブレイクアウト構成。表示幅トグルで measure の効きを実演 -->
<div class="pm" id="pmRoot">
<div class="pm__progress" id="pmProgress"><span></span></div>
<div class="pm__toolbar">
<span class="pm__toolbar-label">表示幅</span>
<div class="pm__btns">
<button type="button" data-w="pc" class="is-active">PC</button>
<button type="button" data-w="tablet">タブレット</button>
<button type="button" data-w="mobile">スマホ</button>
</div>
</div>
<div class="pm__frame" id="pmFrame">
<header class="pm__masthead">
<span class="pm__kicker">DESIGN NOTES</span>
<h1>読みやすさは、行の長さで決まる</h1>
<p class="pm__meta">編集部 ・ 2026年7月 ・ 読了目安 4分</p>
</header>
<article class="pm__article">
<p class="pm__lead">
本文の読みやすさは、フォントサイズや行間だけでなく「1行の文字数(measure)」に大きく左右されます。
長すぎる行は視線が迷子になり、短すぎる行は改行が多く落ち着きません。
</p>
<p>
欧文タイポグラフィの経験則では、1行あたり45〜75文字(日本語ではおよそ35〜40字)が読みやすいとされています。
CSSでは <code>max-width: 65ch</code> のように <code>ch</code> 単位を使うことで、
フォントサイズに応じて自然に測りやすい行長を保てます。
上の「表示幅」を切り替えると、画面が広がっても本文だけは読みやすい幅に留まるのが分かります。
</p>
<h2>図版だけは、はみ出させる</h2>
<p>
本文は読みやすい幅に収めたい一方で、写真や図版は画面いっぱいに見せたいことがあります。
その場合は本文コンテナをグリッドにして、通常の要素は中央カラムに、図版だけ全幅のカラムへ配置する
「ブレイクアウト(breakout)」というテクニックが使えます。
</p>
<figure class="pm__breakout">
<img src="https://picsum.photos/seed/prosemeasure01/1600/700" alt="" loading="lazy">
<figcaption>本文の幅を超えて全幅に広がる図版。視覚的なリズムの区切りにもなる。</figcaption>
</figure>
<p>
実装は <code>grid-template-columns</code> で
「余白 / 本文幅 / 余白」の3カラムを定義し、通常の子要素は中央カラムに配置、
図版だけ <code>grid-column: 1 / -1</code> で全カラムを貫通させるだけです。
</p>
<blockquote class="pm__pullquote">
「余白の設計は、何を読ませ、何を見せるかの意思表示である。」
</blockquote>
<p>
この構成なら、記事内に何度図版を挟んでも本文の読みやすさを損なわず、
かつ図版の見せ場だけはしっかりと画面を使い切ることができます。
レスポンシブ対応も <code>min()</code> 関数ひとつで済み、追加のメディアクエリはほぼ不要です。
</p>
</article>
</div>
</div>
【CSS】
/* 全体:本文は65ch幅の読みやすいレイアウト、図版だけ全幅にブレイクアウトする記事構成 */
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Hiragino Sans", "Yu Gothic", system-ui, sans-serif;
background: #faf9f6;
color: #262320;
min-height: 100vh;
display: block; /* ビルダーの flex 中央寄せを解除し、記事を自然な縦積みで表示 */
}
/* ルート:width:100% が無いと親の flex 中央寄せで潰れるため必須 */
.pm { width: 100%; padding: 0 0 80px; position: relative; }
/* 上部の読了プログレスバー(幅は JS がスクロール量に応じて更新) */
.pm__progress { position: fixed; top: 0; left: 0; width: 100%; height: 3px; background: transparent; z-index: 10; pointer-events: none; } /* sticky ツールバー上端をクリック遮断しないよう pointer-events:none */
.pm__progress span { display: block; height: 100%; width: 0%; background: #b4541f; }
/* 表示幅トグル:ボタンでフレーム幅を変え、65ch measure とブレイクアウトの効きを実演 */
.pm__toolbar {
position: sticky; top: 0; z-index: 6;
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
padding: 14px 20px; margin-bottom: 4px;
background: rgba(250, 249, 246, .92); backdrop-filter: blur(6px);
border-bottom: 1px solid #ece6d7;
}
.pm__toolbar-label { font-size: 11px; font-weight: 800; letter-spacing: .12em; color: #8a8478; }
.pm__btns { display: flex; gap: 6px; }
.pm__btns button {
font-size: 12.5px; font-weight: 700; padding: 6px 14px; border-radius: 999px;
border: 1px solid #e0d8c6; background: #fff; color: #6b6456; cursor: pointer;
transition: background .15s, color .15s, border-color .15s;
}
.pm__btns button:hover { background: #f3ece0; }
.pm__btns button.is-active { background: #b4541f; border-color: #b4541f; color: #fff; }
/* 表示幅フレーム:ここに幅トグルが効く。中の本文は 65ch を保ち、図版だけ全幅にはみ出す */
.pm__frame {
width: var(--pm-w, 100%);
max-width: 100%;
margin: 0 auto;
padding: clamp(20px, 5vw, 40px) 0 0;
transition: width .35s ease;
}
.pm__masthead { text-align: center; padding: 0 20px 32px; }
.pm__kicker { display: inline-block; font-size: 11px; font-weight: 800; letter-spacing: .28em; color: #b4541f; margin-bottom: 10px; }
.pm__masthead h1 { font-size: clamp(24px, 4.4vw, 36px); line-height: 1.4; font-weight: 800; max-width: 20ch; margin: 0 auto 10px; }
.pm__meta { font-size: 12.5px; color: #8a8478; }
/* ブレイクアウト・グリッド:余白 / 本文幅(65ch) / 余白 */
.pm__article {
display: grid;
grid-template-columns: minmax(20px, 1fr) min(65ch, 100% - 40px) minmax(20px, 1fr);
}
.pm__article > * { grid-column: 2; }
.pm__article > .pm__breakout { grid-column: 1 / -1; }
.pm__lead { font-size: 17px; line-height: 2; font-weight: 700; margin-bottom: 20px; }
.pm__article p { font-size: 15.5px; line-height: 2; margin-bottom: 18px; color: #35312b; }
.pm__article code {
font-family: ui-monospace, "SFMono-Regular", Menlo, monospace;
font-size: 13px; background: #efe9dd; color: #8a4a1c;
padding: 2px 6px; border-radius: 5px;
}
.pm__article h2 {
font-size: clamp(19px, 2.8vw, 24px); font-weight: 800;
margin: 30px 0 14px; padding-top: 16px; border-top: 1px solid #e6e0d2;
}
/* 全幅の図版(ブレイクアウト) */
.pm__breakout { width: 100%; margin: 30px 0; }
.pm__breakout img {
width: 100%; aspect-ratio: 1600 / 700; max-height: 56vh; object-fit: cover;
display: block; border-radius: 4px; background: #ece6d7; /* 読み込み前のプレースホルダ色(CLS対策) */
}
.pm__breakout figcaption {
width: min(65ch, 100% - 40px); margin: 10px auto 0;
font-size: 12.5px; color: #8a8478; text-align: center;
}
/* 引用 */
.pm__pullquote {
font-size: clamp(18px, 2.4vw, 22px); font-weight: 700; line-height: 1.7;
color: #b4541f; padding: 4px 0 4px 20px; margin: 26px 0;
border-left: 3px solid #b4541f;
}
@media (prefers-reduced-motion: reduce) {
* { scroll-behavior: auto; }
.pm__frame, .pm__btns button { transition: none; }
}
【JavaScript】
// (A) 表示幅トグル:フレーム幅を変え、65ch の measure とブレイクアウトの効きを実演(null安全)
(() => {
const root = document.getElementById('pmRoot');
const frame = document.getElementById('pmFrame');
if (root && frame) {
// 2カラム一覧のカード(フレーム幅 約475px)でも3段が見分けられるよう、tablet/mobile はそれより狭くする。
// pc=100% は詳細モーダル(〜900px幅)で 65ch に余白が付き、measure の効きがはっきり分かる。
const widths = { pc: '100%', tablet: '380px', mobile: '290px' };
const buttons = root.querySelectorAll('[data-w]');
buttons.forEach((btn) => {
btn.addEventListener('click', () => {
const w = widths[btn.getAttribute('data-w')] || '100%';
frame.style.setProperty('--pm-w', w);
buttons.forEach((b) => b.classList.remove('is-active'));
btn.classList.add('is-active');
// 幅変更で記事高さが変わるため、プログレスバーを即再計算(別IIFEのresizeハンドラを起こす)
window.dispatchEvent(new Event('resize'));
});
});
}
})();
// (B) スクロール位置に応じて上部の読了プログレスバーの幅を更新(null安全)
(() => {
const bar = document.getElementById('pmProgress');
const article = document.querySelector('.pm__article');
const fill = bar ? bar.querySelector('span') : null;
if (!bar || !article || !fill) return;
const update = () => {
const rect = article.getBoundingClientRect();
const total = rect.height - window.innerHeight;
const scrolled = -rect.top;
const pct = total > 0 ? Math.min(100, Math.max(0, (scrolled / total) * 100)) : 0;
fill.style.width = pct + '%';
};
window.addEventListener('scroll', update, { passive: true });
window.addEventListener('resize', update);
update();
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。