overflow:hidden や preventDefault(); でスクロールを禁止できないときの対処法

はじめに

Google で「スクロール 禁止」と検索すると以下のようなものがヒットします。

CSS
html, body{
  overflow: hidden;
}
JavaScript
document.addEventListener('touchmove', function(e) {e.preventDefault();});

しかし上は iOS では動作せず、下も Chrome 54 から動作しなくなったようです:

SS 2018-04-17 at 0.49.32.png

[Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive.
https://www.chromestatus.com/features/5093566007214080
https://developers.google.com/web/updates/2017/01/scrolling-intervention

ブラウザはスクロール時に起こるイベント (touchmove など) が起こるとき、addEventListener などによって処理が登録されていると、その処理に preventDefault(); (スクロールをキャンセルする関数)が含まれているかどうか確認します。この確認している間はスクロールは行われません。そのため、時間がかかる処理がある場合は、スクロール速度が低下して動作がカクカクになってしまうという問題がありました。

そこで passive というオプションが追加されました。
このオプションを true にすると、「この処理に preventDefault(); は含まれていないのでスクロールを行っていいですよ」とブラウザに明示することができます。
これによりブラウザは処理を行う前にスクロールをすることができるので、スクロール速度を改善することができます。

比較動画はこちら (左が従来のもの、右が {passive: true} を指定したものです):

Side by side: passive event listeners
https://youtu.be/65VMej8n23A

Chrome 54 以降ではデフォルトでこの passivetrue となっており、preventDefault(); を使用する場合には {passive: false} を指定しなくてはなりません。

対処法

addEventListener の第3引数として {passive: false} を指定する。

JavaScript
document.addEventListener('touchmove', function(e) {e.preventDefault();}, {passive: false});

これはモバイルでしか動作しないため、デスクトップ用に overflow: hidden も必要です。

まとめ

CSS
html, body{
  overflow: hidden;
}
JavaScript
document.addEventListener('touchmove', function(e) {e.preventDefault();}, {passive: false});

おまけ

touch-action というすべてのタッチ操作を無効化する CSS プロパティもあります。
今後はこちらを使っていくことが推奨されていますが、Safari はまだ対応していません。

CSS
touch-action: none;

iOS Safari only supports auto and manipulation.
https://caniuse.com/#feat=css-touch-action

参考

Passive Event Listeners によるスクロールの改善
https://blog.jxck.io/entries/2016-06-09/passive-event-listeners.html

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.