Edited at

2019年、JavaScriptでのスクロール一時禁止はこれだ!(スマートフォン)


はじめに

グローバルナビを開いた時、モーダルを開いた時にスクロールを一時的に禁止したい。

こういう仕様はよくありますが、スマートフォンにて久々にハマってしまったのでメモがてら書いてみます。


これでいいと思ってた

モーダル開いた時や、ナビ開いた時にtouchmoveイベントをキャンセルすればいいんだよね!

よしこれでどう?

//スクロール禁止

$(document).on('touchmove.scroll', function(event) {event.preventDefault();});

//スクロール復帰
$(document).off('touchmove.scroll');


Chromeで見てたらconsoleにエラーが!

コンソールに出てしまったエラー

動かない。

上記のようなエラーが出ていました。なにこれ。

色々ページを調べていくと。。

Making touch scrolling fast by default | Web | Google Developers |

上記ページにこんなことが。


The problem is that most often listeners will not call preventDefault(), but the browser needs to wait for the event to finish to be sure of that.

問題は、たいていのイベントリスナーはpreventDefault();の呼び出しをしませんが、ブラウザは「preventDefault();の呼び出しが無い」こと確認するまでイベントを待つ必要があると言うことです。(筆者テキトー訳)


なるほど。

スクロールする時、登録されたすべてのイベントの中にpreventdefault();がないかを確認するまで、

JavaScriptが止まっちゃう。

だから遅延が発生しパフォーマンスが下がってしまうというわけか。

こういうのをスクロールジャンクと呼ぶらしいです。


スクロールジャンクを止めるために、ブラウザ側が手を打った

このスクロールジャンクは、JavaScriptのaddEventListenerの引数で止めることができるようです。(jQueryではなく!)

passiveという引数をtrueにすることで、

「リスナーで登録されたpreventDefault();は一切実行しないから!!」

という宣言ができるとのこと。

確かにこうやって宣言できれば、「登録されたすべてのイベントの中にpreventdefault();がないかを確認する」作業をしなくてよいわけだから、スクロールジャンクは無くなりますね!

でも、passiveという引数をtrueにした覚えないんですけど...?

というかjQueryで書いたので、引数すら意識してませんでしたけど。。

どうしてなの?

と思ったら下記のページに答えが。

EventTarget.addEventListener() | MDN


According to the specification, the default value for the passive option is always false.

...

...

To prevent this problem, some browsers (specifically, Chrome and Firefox) have changed the default value of the passive option to true for the touchstart and touchmove events on the document-level nodes Window, Document, and Document.body.




仕様によると、addEventListenerのpassive引数のデフォルトは'false'です。

...

...

スクロールジャンクの問題を防ぐために、ChromeやFirefoxなどのブラウザは、window、document、document.bodyに付与されたtouchstart・touchmoveイベントのpassive引数のデフォルト値を'true'にしています。(筆者テキトー訳)


なるほどなるほど。

ブラウザの方で気を利かせて'true'にしてくれていたのか!

ありがとう、でも今回はpreventDefault();を効かせたいので、falseにさせてもらいます。


{ passive: false }にしてみたけどなんか上手くいかない

よし、じゃあこれでどうだ!

//スクロール禁止

document.addEventListener('touchmove', function(event) {event.preventDefault()}, { passive: false });
//スクロール復帰
document.removeEventListener('touchmove', function(event) {event.preventDefault()}, { passive: false });

しかし。

禁止はできるけど、復帰ができない。

どうやらremoveEventListenerがうまくいってないようだ。

調べてみると。。

event listener - removing javascript eventlisteners - Stack Overflow

無名関数はremoveEventListenerできないらしいです。

しまった、ここでjQueryしか使ってこなかったことがバレた。。

勉強しないとな。


結論 : これでうまくいきました

function handleTouchMove(event) {

event.preventDefault();
}
//スクロール禁止
document.addEventListener('touchmove', handleTouchMove, { passive: false });
//スクロール復帰
document.removeEventListener('touchmove', handleTouchMove, { passive: false });

以上です。

Vanilla JavaScript、しっかり勉強します。