[モーダル] スクロールバー表示時のガタつきをなくす実装方法
概要
モーダルウィンドウを表示または非表示にした際、スクロールバーが表示されることでコンテンツがズレるケースに対処する。
解決方法
1.スクロールバーの幅を取得
まずはスクロールバーのサイズを確保しておく。CSSから値を取得できるようにCSS Custom Propertiesにスクロールバーの幅を設定する。
function observeScrollbarWidth() {
const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth;
document.documentElement.style.setProperty('--scrollbar-width', `${scrollBarWidth}px`);
}
window.addEventListener('load', observeScrollbarWidth);
window.addEventListener('resize', debounce(observeScrollbarWidth));
上記のようにロード時とリサイズ時にスクロールバーの幅を取得し、CSS Custom Propertiesに設定する。resizeの場合はdebounce
関数を使ってリサイズ時のパフォーマンスを下げないようにしている。
2.スクロールバーの幅を考慮したスタイルを適用
今回はdialog
要素が開いている時にスクロールバーの幅を考慮したスタイルを適用する例を示す。擬似クラスの:has()
を使えば、CSSだけでモーダル表示時にスクロールバーを非表示にできる。
body {
&:has(dialog[open]) {
overflow: hidden;
& {
padding-inline-end: var(--scrollbar-width, 0);
}
}
}
dialog
表示時にスクロールバーの幅分の余白をもたせる必要があるため、dialog
表示時はbody
要素にスクロールバーのサイズ分のpadding-inline-end
を設定する。
プリプロセッサを利用していない(CSS)場合
body:has(dialog[open]) {
overflow: hidden;
}
body:has(dialog[open]) {
padding-inline-end: var(--scrollbar-width, 0);
}
CSSだけで実装をしていない場合は、JSで付与した状態(CSSクラス)に対して同じようにpadding-inline-end
を適用すると良い。
const dialog = document.querySelector('dialog');
if (dialog && dialog.open) {
document.body.classList.add('open-dialog');
} else {
document.body.classList.remove('open-dialog');
}
body.open-dialog {
padding-inline-end: var(--scrollbar-width, 0);
}
論理プロパティを利用している理由
padding-inline-end
を使っている理由は、LTR(左から右)とRTL(右から左)の両方に対応するため、論理プロパティを指定した。もし柔軟に対応をしなくて良い場合などは、LTRの場合はpadding-right
、RTLの場合はpadding-left
を使うことで同様に余白を設定できる。
その他
macOS開発者向けの設定
macOSはスクロールバーを設定で非表示にできるため、macOSを利用している開発者はスクロールバーのガタつきに気付きにくい。
- システム設定の「外観」
- 「スクロールバーを表示」を「常に表示」に変更
上記の設定をすることで、macOSでもスクロールバーの表示・非表示時のガタつきを確認できる。