モーダルやドロワーメニューなどを実装する際に、表示中はページ全体のスクロールを禁止させたいということがよくあるかと思います。
僕も以前ドロワーメニューのjQueryプラグインを作ったときに、ページ全体のスクロールを止めるために試行錯誤してました。
そのときの知見を踏まえて、改めて現時点でのベストプラクティスを考察してみました。
ダメなパターン1
body全体に overflow をかけることで、スクロールを制御する。
body { overflow: hidden; }
多分誰もが最初に思いつきそうなやつです。
一応これでもPCやAndroidではうまくいきますが、iOS Safariでは効きません。
よってiOSも対応させたい場合(がほとんどだと思いますがw)は、他の方法で対応するしかありません。
ダメなパターン2
windowオブジェクトのtouchmoveイベントを無効化する。
window.addEventListener('touchmove', function(event) { event.preventDefault(); });
ググるとわりとよく出てくるやつです。
この方法はタッチイベントのみを制御するので、デスクトップ端末用にパターン1と併用することになるでしょう。
iOSでも想定通りの挙動になるため、一見うまくいっているように思えます。
しかしこのやり方では一つ落とし穴があります。
それは他の要素でスクロールさせたい部分があった場合、それらも全てスクロールされなくなってしまうことです。
例えばtextareaであったり、モーダル内にコンテンツが多い場合はその中でスクロールさせることもあるでしょう。
この方法だと、それらのスクロールも禁止されてしまうため、もし他の要素はスクロールさせたいといった場合は、他の方法で対応するしかありません。
ベストプラクティス
結論としては、下記のやり方が現時点ではベストプラクティスなんじゃないかと思います。
それは、body全体にposition fixedをかけて、topの値をスクロール値に設定してしまうというもの。
言葉で説明しても分かりづらいですねw
ソースコードにすると、こんな感じです。
.no_scroll { position: fixed; left: 0; right: 0; overflow: hidden; }
const body = document.getElementsByTagName('body')[0]; const btn = document.getElementById('btn'); let scrollTop = 0; btn.addEventListener('click', () => { if (body.classList.contains('no_scroll')) { // スクロール開放 body.style.top = ''; body.classList.remove('no_scroll'); window.scrollTo(0, scrollTop); } else { // スクロール禁止 scrollTop = window.scrollY; body.style.top = (scrollTop * -1) + 'px'; body.classList.add('no_scroll'); } });
説明のためにトリガーは適当にボタンのクリックイベントにしておきます。
重要なのは14行目あたりのbodyのstyleに「top」をセットしている箇所になります。
position fixedにすることで強制的にスクロールできないようにしてしまいますが、それだけだと位置が一番上に来てしまうので、現在のスクロール値を「top」に設定することで、スクロール位置があたかもそのまま固定されているように見せているという仕組みですね。
この方法でiOSを含め全てのブラウザで同様の挙動にできます。
デメリット
デメリットも無いわけではないです。
iOS Safariの上下のバーが引っ込んでいるときにこの処理を適用させると、実際のスクロール位置は 0 になるため、上記のキャプチャのように上下のバーが出てきてしまいます。
このへんも調整できたら最高なんですが、現時点ではこの解消法はなさそうです。
とはいえ、まあ個人的にはあまり気にならないレベルかなとも思いますが。
最後に
ページ全体のスクロールを禁止させる方法を解説してきましたが、もし今後iOSのアップデートなどでもっとスッキリしたやり方が可能になる可能性も(かなり低めですがw)なくはないかなと思うので、もしもっと良い方法が見つかったら、また追記したいと思います。
コメント