モーダル表示時のスクロールバー調整やオーバレイ表示を極力JavaScriptなしで実装する方法
前提
モーダルは<dialog>
要素を使用するが、<div>
で実装したものでも問題ない。
構図は以下を想定する。
<body>
<dialog></dialog>
<div class="overlay"></div>
</body>
dialogのスタイル例
dialog {
position: fixed;
isolation: isolate;
content-visibility: hidden;
}
dialog[open] {
z-index: 1;
padding: 0;
border: none;
content-visibility: visible;
}
モーダル表示時のオーバーレイを兄弟セレクタで表示する
dialog
が開いたときにopen
属性が付与される。それを利用してオーバーレイを表示する。
.overlay {
position: fixed;
inset: 0;
z-index: -1;
visibility: hidden;
background-color: var(--overlay-backgrounds);
opacity: 0;
}
dialog[open] ~ .overlay {
visibility: visible;
opacity: 1;
}
隣接セレクタではなく兄弟セレクタを利用しているのは、dialog
と.overlay
の間にサードパーティ製の要素が挟まることを考慮しているためである。
モーダル表示時にスクロールバーを非表示にする
:has
セレクタを利用することでdialog
が表示されている(open属性)際、body要素にoverflow: hidden
を適用できる。
body:has(dialog[open]) {
overflow: hidden;
}
スクロールバーの横幅を取得する
macOSの設定によってはスクロールバーは常に表示されないため気付かれないことも多いが、スクロールバーを非表示にすると横幅分のスペースがズレてしまう。
スクロールバーの横幅自体はwindow.innerWidth - document.documentElement.clientWidth
で取得できる。これをCSS Custom Propertiesに格納しておき、スタイルで利用できるようにしておく。
export default function setScrollbarWidth() {
const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth;
document.documentElement.style.setProperty('--scrollbar-width', `${scrollBarWidth}px`);
}
window.addEventListener('load', setScrollbarWidth);
body:has(dialog[open]) {
overflow: hidden;
padding-right: var(--scrollbar-width, 0);
}
var()
関数の第二引数は、カスタムプロパティが未定義の場合に適用される値である。不正な値の場合に予想外の数値が適応されないように0
を指定しておく。
論理プロパティとしてpadding-inline-end
を利用をしても良い。
position: fixed要素にスクロールバー分の余白を付与する
bodyに対して余白を付与する形を取ったが、サイトのUIによっては問題がある。例えば、固定ヘッダやトップに戻るボタンのようなposition: fixed
などで固定されたUIの場合、そのUIだけスクロールバー分の余白がカクついてしまう。
命名は何でも良いが、data-floating
属性というものを用意して、対象の要素の付与してスクロールバー分の余白を付与する。
body:has(dialog[open]) {
overflow: hidden;
}
body:has(dialog[open]),
body:has(dialog[open]) [data-floating] {
padding-right: var(--scrollbar-width, 0);
}
別の例
見通しが悪そうなため、ネスト記法が使えるケース(SCSSなど)を記載する。
body {
&:has(dialog[open]) {
overflow: hidden;
&,
[data-floating] {
padding-right: var(--scrollbar-width, 0);
}
}
}
<body>
<header data-floating></header>
<dialog></dialog>
<div class="overlay"></div>
</body>
おわり
スクロールバーの横幅を取得する部分だけJavaScriptを利用しているが、それ以外はCSSだけで実装できる。
:has()
セレクタが使えない場合、モーダルの表示状態をJavaScriptで監視して、body要素にクラスを付与するなどの処理が必要になるため、それがなくなるだけでも実装が簡単になる。