HTML
CSS
JavaScript
Chrome
iOS

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

検索すると overflow:hidden や preventDefault(); を使うとスクロールを無効化することができるといった情報が出てきますが、iOS や Chrome 54 以降では動作しなくなっています。

preventDefault(); が動作しない理由

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

iOSはoverflow:hidden;でスクロールを無効にできない - Qiita
https://qiita.com/mimoe/items/f5f668cebb697d073553