この記事では、mainコンテンツとサイドバーの文章量が違っても、追従する要素がちょうどよい位置で止まる仕組みを解説します。ポイントは、Flexboxのalign-items: stretch;で左右を高い側へ揃えること、伸びたサイドバーの子要素へposition: sticky;を指定すること、祖先要素のoverflowを確認することの3つです。当サイトで使用しているCSS Grid版も後半で紹介します。
読者mainの記事は長く、サイドバーは短いのに、目次がちょうどよい位置で止まるのはなぜですか?
木村Flexboxならalign-items: stretch;で外側のサイドバーをmainと同じ高さまで伸ばし、その子要素の目次だけをposition: sticky;で追従させます。
読者JavaScriptでmainの高さを取得して、サイドバーへ設定しているのではないんですか?
木村現在の当サイトでは、高さの計算にJavaScriptを使っていません。CSS Gridが左右の範囲を作り、stickyが親要素の終端を判断します。
読者それなら、記事の長さが変わってもコードを修正しなくてよいのでしょうか?
木村はい。レイアウト後の高さに合わせてブラウザが処理するため、文章量が変わっても自動で調整されます。重要なのは外側と内側を分けることです。
先に動画で確認したい方は、こちらをご覧ください。Flexboxのalign-items: stretch;、stickyを子要素へ指定する理由、祖先要素のoverflowを確認するポイントを、実際の動きとあわせて解説しています。
今回の実装で重要な3つのポイント
先に結論を整理します。Flexboxで左右の高さを揃え、伸ばしたサイドバーを追従範囲として使い、その子要素だけをstickyにします。うまく動かない場合は、親だけでなく、さらに上の祖先要素まで確認することが大切です。
align-items: stretchで左右を高い側へ揃える
Flexboxでは、親要素へalign-items: stretch;を指定すると、子要素が交差方向へ伸びます。横並びのレイアウトでは縦方向へ伸びるため、mainの内容が長ければ、短いサイドバーもmainと同じ高さまで広がります。
stretchはFlexboxの初期状態でも働きますが、align-items: start;やサイドバーのalign-self: start;で上書きすると高さは揃いません。この記事では、役割が分かるようにstretchを明示します。
position: sticky;を指定するのは、外側のサイドバーではなく、その中にある目次や人気記事です。外側のサイドバーは「移動できる範囲」、内側の要素は「実際に追従するパーツ」という役割になります。
外側のサイドバーがmainと同じ高さまで伸びているため、子要素はその範囲内を追従し、サイドバーの下端へ到達すると自然に止まります。
overflowは親より上の祖先要素まで確認する
stickyが効かないときは、直接の親だけでなく、その親、さらに上の親まで確認します。途中の祖先要素にoverflow: hidden;、auto、scrollなどがあると、stickyが基準にするスクロール領域が変わり、画面に対して期待どおり追従しないことがあります。
stickyの指定箇所だけを見ても原因が分からない場合は、開発者ツールで外側へ一つずつたどり、祖先要素すべてのoverflowを確認してください。
Flexboxとstickyのシンプルな実装
実装は、HTMLで役割を分け、Flexboxで左右を揃え、サイドバーの子要素へstickyを指定するだけです。固定の高さやJavaScriptによる計算は使いません。
HTMLを3つの役割に分ける
全体を囲む.layout、長い本文を入れるmain、追従範囲になるasideを用意します。追従させたい目次や人気記事は、asideの子要素として配置します。
<div class="layout">
<main class="main-content">
<h1>記事タイトル</h1>
<p>ここに長さが変わる記事本文が入ります。</p>
</main>
<aside class="sidebar">
<section class="sidebar__sticky">
<h2>目次または人気記事</h2>
</section>
</aside>
</div>Flexの親へalign-items: stretchを指定する
.layoutをFlexコンテナにし、align-items: stretch;を指定します。mainの文章量が多い場合は、その高さを基準にサイドバーも縦へ伸びます。
.layout {
display: flex;
align-items: stretch;
gap: 42px;
}
.main-content {
flex: 1 1 auto;
min-width: 0;
}
.sidebar {
flex: 0 0 300px;
}
.sidebar__sticky {
position: sticky;
top: 24px;
}top: 24px;は、画面上端から追従させる位置です。固定ヘッダーがある場合は、ヘッダーの高さと余白に合わせて調整してください。
高さを固定しなくても内容に合わせて変わる
mainへ文章や画像を追加するとFlexコンテナが高くなり、サイドバーも自動で同じ高さまで伸びます。height: 2000px;のような固定値は不要です。
実行後は、.sidebar__stickyが指定位置で追従を始め、伸びた.sidebarの終端で止まります。これが、左右の内容量が変わってもちょうどよい位置でスクロールが終了する仕組みです。
当サイトで使用しているCSS
当サイトではCSS Gridを採用し、mainとサイドバーを.content-layoutで横並びにして、外側の.sidebarをGridの行まで伸ばしています。前章のFlexbox版でも同じ追従動作を作れますが、ここでは実際のサイトコードを確認します。
実際の基本部分は次のとおりです。.content-layoutのalign-items: start;に対し、.sidebarだけがalign-self: stretch;で上書きされます。
.content-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 300px;
align-items: start;
gap: 42px;
}
.sidebar {
position: static;
align-self: stretch;
}
.article-toc {
position: sticky;
top: 108px;
}top: 108px;は固定ヘッダーと余白を避けるための値です。自分のサイトへ取り入れる場合は、ヘッダーの高さに合わせて調整してください。
トップページでは人気記事を追従させる
トップページでは、サイドバー内の人気記事セクションへsticky用のクラスを追加しています。プロフィールや検索欄は通常どおり流れ、人気記事が指定位置へ来たときだけ追従します。
記事詳細ページでは目次を追従させる
記事詳細ページでは、人気記事ではなく、その下にある記事内目次へstickyを指定します。記事本文、関連する記事、コメントなどでmainの高さが変わっても、外側のサイドバーが同じGridの行まで伸びるため、目次はその終端で止まります。
stickyそのものの基本は、position: stickyでサイドバーを追従固定する方法で詳しく解説しています。本記事では、その追従範囲がmainの高さに合わせて決まる構造に焦点を当てています。
JavaScriptで高さを計算する方法との違い
mainとサイドバーの高さを合わせる方法には、JavaScriptで要素の高さを取得する方法もあります。ただし、今回のような2列レイアウトとstickyの停止位置であれば、まずFlexboxまたはCSS Gridだけで実装できないかを確認するのがおすすめです。
CSSなら文章量の変化へ自動で対応できる
CSS Gridはレイアウト時に内容の大きさを反映します。記事の追記、画像の追加、関連記事の増減などがあっても、Gridの行とサイドバーの範囲が自動で更新されます。
高さを数値として保存しないため、読み込み時や画面サイズ変更時に再計算するコードも不要です。
既存記事はJavaScript方式を扱っている
当サイトには、メインコンテンツとサイドバーの高さをJavaScriptで動的に調整する記事もあります。そちらはDOMContentLoadedやoffsetHeightを使い、高さを数値として取得する方法です。
本記事は重複ではなく、現在のサイトで採用しているCSS中心の実装を説明しています。単純な2列レイアウトなら、コードが少なく変更にも強いCSS方式が使いやすいです。
JavaScriptが必要になる場合
高さの数値を別の処理で利用する場合や、複数の要素を独自条件で連動させる場合はJavaScriptが必要になることがあります。見た目を揃えるだけなのか、取得した高さを処理へ使うのかで判断しましょう。
stickyが効かないときの確認点
うまく追従しない場合は、次の順番で確認すると原因を見つけやすくなります。特に見落としやすいのは、Flexboxの伸び方と、離れた祖先要素に指定されたoverflowです。
align-itemsまたはalign-selfがstretchか
開発者ツールで外側の.sidebarを選び、mainと同じ高さまで伸びているか確認します。Flexの親がalign-items: start;になっていたり、サイドバーへalign-self: start;が指定されていたりすると、サイドバーは内容の高さまでしか伸びません。
追従範囲が短い場合は、Flexの親をalign-items: stretch;に戻すか、サイドバーへalign-self: stretch;を指定します。そしてstickyは、伸ばしたサイドバーの子要素へ付けます。
overflowは祖先要素をすべて確認する
overflow: hidden;が直接の親になくても安心できません。その親より上のラッパーやページ全体を囲む要素に指定されていても、stickyの基準へ影響します。
sticky要素からbodyまで祖先要素を一つずつたどり、overflow: hidden;、auto、scrollがないか確認します。必要のない指定なら外し、必要な場合はHTML構造やスクロールさせる要素を見直します。
スマホと高さのある目次に対応する
スマホでは横並びを解除し、stickyも通常配置へ戻すと読みやすくなります。目次が画面より高い場合だけ、sticky要素自身に最大高さと内部スクロールを設定します。
.sidebar__sticky {
max-height: calc(100vh - 48px);
overflow-y: auto;
}
@media (max-width: 980px) {
.layout {
display: block;
}
.sidebar__sticky {
position: static;
max-height: none;
overflow: visible;
}
}ここでのoverflow-y: auto;は、画面より高いsticky要素の内部だけをスクロールさせる意図があります。祖先要素へ不用意に指定する場合とは役割が異なります。
まとめ
- Flexboxでmainとサイドバーの高さを揃え、サイドバーの子要素へstickyを指定すると、内容量が変わっても追従要素を自然な位置で止められます。
- Flexの親へ
align-items: stretch;を指定し、左右を高い側へ揃える - 外側のsidebarは追従範囲として伸ばし、stickyはその子要素へ指定する
- 効かない場合は
align-itemsとalign-selfを確認する overflow: hidden;は直接の親だけでなく、さらに上の祖先要素まで確認する- 高さの固定値やJavaScriptによる計算は基本的に必要ない
- まずは「外側をstretchで伸ばす」「内側をstickyにする」という2つの役割を分けてください。動かないときは祖先要素を上へたどると、原因を見つけやすくなります。

align-items: stretchがstickyへ影響するところで詰まっていました。親側をflex-startにする理由まで分かり、手元のレイアウトでも直せました。ありがとうございます。
ありがとうございます。解決してよかったです。