CSSの overflow によって要素からはみ出した部分の制御を行う場合、最近だと hidden よりも新しい clip が便利に扱われる場面が多いかと思います。
X方向の overflow-x 、Y方向のoverflow-y には片方を auto(scroll) / hidden にするともう片方も強制的に auto / hidden になるという(そもそもがバグみたいな)仕様があり、これによるトリミング処理の不便さを解決するために新しく策定されたのが overflow: clip; です。
また、新しいといえど2022年9月頃にはSafari, Firefoxともに対応されたため、モダンブラウザ対応においては比較的安心して使ってもよい(Widely available)プロパティとされています。
clip
切り取られた領域の外側にあふれたコンテンツは表示されず、ユーザーエージェントはスクロールバーを追加せず、プログラムによるスクロールも行われません。
この要素ボックスはスクロールコンテナーにはなりません。
ただこの仕様を聞くと、「 <body> に当ててしまえばかのにくきSafariも含むスクロール禁止がclip一つで済むのでは?」と考えてしまいますが、そうは問屋が卸しません。はよ卸さんかいApple。
※iOS Safari向けの対策についてはよければ過去記事を参照してください。
overflow: clip; でPC向けのスクロール禁止は実際に機能はするのですが、 <body> に適用することで発生した一部バグのような挙動があったので、調査結果をまとめておきます。
前提知識:bodyに書いたoverflowの特殊仕様
:root (HTMLなら <html> )要素、または全ての可視コンテンツが入る <body> に対して overflow プロパティが設定された場合、CSSで定義された「Overflow Viewport Propagation」と呼ばれる仕様によって、その値はビューポートに対しても伝播(Propagation)されます。
ざっくりというと、 <body> にデフォルト値 overflow: visible; 以外の値が設定された時は、画面に見える範囲(ビューポート)もその overflow の値と同じ影響を受けるという仕様です。
つまり body { overflow: hidden; } でも今見ている画面がそのまま切り取られたような形にはなるので、実質 clip と同じ状況になります。そのためPCのスクロール禁止を行いたいだけであれば、 hidden で事足りるというのがCSSの仕様に基づいた見解となるようです。
※なお <html> ( :root )・<body> どちらもに overflow: hidden; を書かないと効かない!みたいに書かれている記事も多いのですが、ルート要素 <html> に overflow の指定がなければ次点として <body> を参照するため、body { overflow: hidden; } だけで支障ないようです。
バグ(?)報告:なぜclipではダメなのか?
「今見ている画面がそのまま切り取られたような形になる」ために指定するプロパティとなると、それこそ hidden (余剰分を隠す)よりも clip (余剰分をカット)が正しく思えますが……。
body { overflow: clip; } の関連処理を行ったサイトを🦊Firefox(150.0.3)で閲覧した際に「 overflow: clip; を解除してもスクロールができなくなる 」現象が発生しました。
サンプル
具体的な状況としては .is-loading のようなクラスを <body> に付与した状態で、サイトの読み込み中はスクロールを制御し、ローディング完了時そのクラスを外す(デフォルト状態に戻す)ことで、以降は通常通りにサイト上のスクロールを可能にしたい〜といった場面を想定します。
<body class="is-loading">
...
</body>
body { /* overflow: visible;(デフォルト値) */ }
body.is-loading {
overflow: clip; /* ロード中は画面を切り取りたい(だけなのに……) */
}
window.addEventListener('load', function () {
// ロード完了後にクラスを外す(そうすれば動くはず……)
document.body.classList.remove('is-loading');
});
こちらでChromeやiOS Safariは問題なくロード完了後のスクロールが動作するのですが、Firefoxのみ overflow: clip; 適用中の状態が残ってしまったかのようにスクロールできなくなります。
対策と解説
似たような現象のバグ報告は見つけられなかったのですが、理屈としてはおそらくbody→Viewportへの overflow の状態の伝播がうまく行われておらず、 clip → visible (デフォルト値)に戻るだけではブラウザ側の内部状態がリセットされていない状態のようでした。
そのため、Firefoxでも正しく動作させるためには body.is-loading の値を clip ではなく同等の動作となる hidden にする・またさらに念を押すなら .is-loaded などのクラスを付与して visible 以外の値 overflow: auto; への変化をきちんとを明示することが良さそうでした。
body {
/* overflow: visible;(デフォルト値) */
}
body.is-loading {
overflow: hidden; /* bodyにclipは付けない */
}
body.is-loaded {
overflow: auto; /* overflowの変化を明示する */
}
window.addEventListener('load', function () {
// ロード完了後にクラスを外し、ロード完了を示すクラスを追加
document.body.classList.remove('is-loading');
document.body.classList.add('is-loaded');
});
ロード完了後の最終的なHTMLがこうなっていれば、いずれのブラウザでも動作するはずです。
<body class="is-loaded">
...
</body>
overflow: auto; の明示で解決する理由については、実際に .is-loaded { overflow: visible; } としてデフォルトの状態に戻しただけではスクロール禁止が改善されなかったことを確認できているためと、おそらくauto をはじめとした visible 以外の値は指定された場合に新しい整形コンテキストを確立するため、Firefoxブラウザ側にも overflow ルールのリセットを明示できていることできちんとスクロールが返ってくる……と解釈できるかと思います。
auto
あふれたコンテンツは要素のパディングボックスで切り取られ、あふれたコンテンツはスクロールバーを使ってスクロールして表示することができます。
scrollとは異なり、ユーザエージェントはコンテンツがあふれた場合にのみスクロールバーを表示します。コンテンツが要素のパディングボックス内に収まる場合、visibleと同じように見えますが、新しい整形コンテキストを確立します。要素ボックスはスクロールコンテナーになります。
まとめ
書いている間に気付いたのですが、先ほどの「Overflow Viewport Propagation」の末尾には以下の記載があります。
If visible is applied to the viewport, it must be interpreted as auto. If clip is applied to the viewport, it must be interpreted as hidden.
これによりViewport…… つまり <body> に書いた overflow の値は以下のように解釈されます。
- overflow:
visible→ overflow:auto - overflow:
clip→ overflow:hidden
よくよく考えてみればビューポートをはみ出した要素が自然にスクロールできるのは自動的に overflow: auto; 扱いになっているから……というのは確かに納得なのですが、そもそもViewportに対しての clip は hidden と解釈されることがきちんとここに明記されていました。
ただしFirefoxのみ動的に overflow の値が変更された場合にこの解釈変更の処理がうまくいっていないようでした(そうなるとやはりバグではあるのでは……?)が、これもオリジナルの hidden に書き換えることで動的変更にも支障が出なくなります。
つまり「 <body> に overflow: clip; は書かない方がいい」ではなく 「書いても(本来なら) overflow: hidden; として解釈されるからわざわざ使う意味がないよ」が正解 のようです。
むしろ今回は偶然にも一部環境のみバグのような挙動を助長する温床になってしまいました。
そのため clip が便利だとしても、これまで数々のサイトのスクロールを止めてきた overflow: hidden; もなかなか捨てたもん(捨てられるもん)じゃない……ということは覚えておきましょう。