overflow-anchorでスクロールアンカリングを制御する
スクロールアンカリングは、表示領域より上でコンテンツの高さが変わったときに、見えている要素がずれないようスクロール位置をブラウザが補正する仕組みです。Safari 27の対応でBaseline 2026に揃いました。補正の仕組みと、overflow-anchor: noneで補正を外すべき場面を整理します。
- Chrome56
- Edge79
- Firefox66
- Safari27
はじめに
記事を読んでいる途中で、上のほうにある画像や広告が遅れて読み込まれて高さが増え、読んでいた行が下へ押し出される。この体験を防ぐために、ブラウザは表示領域の中にある要素を基準に選び、その要素が動いたぶんだけスクロール位置を補正します。これがスクロールアンカリングで、Chrome 56とFirefox 66では2017年から2019年にかけて既定で有効になっていました。
overflow-anchorはこの補正を外すためのプロパティです。補正そのものはブラウザが勝手にやるので、何もしなければ恩恵を受けられます。プロパティが必要になるのは、補正が自分のスクロール制御と衝突する場面だけです。Safari 27でスクロールアンカリングとoverflow-anchorの両方が実装され、Baseline 2026に加わりました。
補正の仕組み
スクロールアンカリングは、スクロールコンテナごとに次の手順で動きます。
- 表示領域に少しでも見えている要素のうち、DOM順で最初のものをアンカーノードに選ぶ。入れ子になっていれば、その中で最初に見えている子孫まで降りる
- レイアウトが変わってアンカーノードのブロック方向の位置が動いたら、その移動量だけスクロール位置をずらす
補正は要素を挿入した直後の同期的な処理の中ではなく、レイアウトの更新に合わせて適用されます。ただし挿入のあとにscrollTopなどスクロール位置を読むと、その時点で補正が入った値が返ります。補正によるスクロールは通常のスクロールと同じ扱いで、scrollイベントも発火します。
補正が行われない条件もあります。スクロール位置が0のときは補正されないので、一番上を見ているときに上へ要素が追加されれば、追加された要素がそのまま見えます。また、アンカーノードからスクロールコンテナまでの経路にある要素でtopやmarginやtransformやpositionの計算値が変わった場合も補正されません。その変更は意図的なレイアウト変更だとみなされ、補正が抑制されます。
途中までスクロールしたリストの先頭に要素を追加し、overflow-anchorがautoのときとnoneのときで見えている要素がずれるかを比較できます。
アイテム 1
アイテム 2
アイテム 3
アイテム 4
アイテム 5
アイテム 6
アイテム 7
アイテム 8
アイテム 9
アイテム 10
アイテム 11
アイテム 12
途中までスクロールした状態で先頭に要素を足します。右の帯は領域全体を縮めた地図で、上の塗りが追加した要素、枠がいま見えている範囲です。autoでは枠が追加した高さのぶん下へ動いて見えているアイテムが留まり、noneでは枠が動かず、見えていたアイテムが下へ押し出されます。
補正を外す場面
overflow-anchorはautoとnoneの2値で、初期値はautoです。noneを指定した要素とその子孫はアンカーノードの候補から外れます。継承はしませんが、子孫には効くので、実質的にその範囲でアンカリングが止まります。
補正が邪魔になる典型は、スクロール位置を自分で管理しているコンポーネントです。
- チャットのログのように、上へスクロールしたら古いメッセージを先頭に足す実装で、追加したあとに自分で
scrollTopを足し戻している場合。補正と自前の調整が二重にかかり、見えている位置が足した高さのぶんずれる - 仮想スクロールで、要素の高さを見積もりから実測値に更新するたびにコンテナの高さを付け替える場合。補正がかかるとリストが勝手に動いて見える
こうしたコンテナにはoverflow-anchor: noneを指定して、スクロール位置の責任を自分のコードに寄せます。
.chat-log {
overflow-y: auto;
/* スクロール位置は自前で制御するので補正を外す */
overflow-anchor: none;
}要素単位で候補から外す使い方もあります。補正が追うのはアンカーノードの上端だけなので、アンカーノード自身の高さが変わっても補正は入りません。読み込み中のスケルトンや高さが決まっていない広告枠が表示領域の上端にあると、それがアンカーノードに選ばれ、読み込み完了で背が高くなったぶん下の内容が押し出されます。そうした要素にoverflow-anchor: noneを付けておくと候補から外れ、直後の要素がアンカーノードになるので、高さの変化が補正されます。
.skeleton,
.ad-slot {
overflow-anchor: none;
}要素が消えるだけなら、この指定は要りません。アンカーノードがDOMから外れるとブラウザが選び直すので、下の内容はその場に留まります。効くのは、要素が残ったまま高さだけ変わる場面です。
表示領域の上端にあるスケルトンが読み込み完了で背が高くなるとき、スケルトンにoverflow-anchor: noneを付けた場合と付けない場合で、直後のアイテムが押し出されるかを比較できます。
アイテム 1
アイテム 2
アイテム 3
アイテム 4
読み込み中のスケルトン
アイテム 6
アイテム 7
アイテム 8
アイテム 9
アイテム 10
スケルトンが表示領域の上端をまたいだ状態で、読み込み完了で背が高くなります。スケルトンがアンカーノードのままだと、その上端は動かないので直後のアイテムが押し出されます。overflow-anchor: noneを付けると直後のアイテムがアンカーノードになり、高さの差だけスクロール位置が補正されて、見えていたアイテムはその場に留まります。
overflow-anchor: noneの子孫でスクロールアンカリングを再度有効にする方法はありません。ただし子孫にあるスクロールコンテナは、自身のスクロールボックスについては再び候補を選び始めます。
おわりに
スクロールアンカリングは、表示領域より上でレイアウトが変わったときに見えている要素を固定する補正で、何もしなくても効きます。overflow-anchor: noneはその補正を外すための手段で、スクロール位置を自分で制御するコンテナや、すぐ消える要素に付けます。Safari 27で補正とoverflow-anchorの両方が揃ったので、これまでChromeとFirefoxだけを想定していたスクロール制御のコードは、Safariでも同じ前提で動くようになります。