その昔、スマホ向けWebページの拡大縮小・スクロールをさせたくない場合にはviewport
にuser-scalable=no
を指定するという手軽な方法が利用可能でしたが、2016年のiOS 10のリリース以降はこの方法が使えなくなったため、JavaScriptを使ってtouchstart
イベントやtouchmove
イベントのハンドラでpreventDefault()
を呼ぶ方法がポピュラーになっていると思います。
が、つい最近iOS 11.3 Safariでこの方法が効かなくなっていることに気づきました。この変更は結構多くのライブラリの動作に影響を与えており、一部の開発者の間で騒ぎになっていてWebKitのバグにも登録されました。しかし、WebKitの開発者ではこれはバグではなく想定された仕様変更だということで、ページ製作者の側が対応する必要があります。
Bug 182521 – REGRESSION (iOS 11.3 beta): touchmove preventDefault() no longer respected
具体的にどんな変更があったかというと、body
、document
、window
のtouchstart
およびtouchmove
イベントハンドラのデフォルト動作が、passive
になったとのこと。passive
なイベントハンドラではpreventDefault()
の呼び出しが無視されてしまうわけです。イベントハンドラの動作オプションについては下記の記事を参考にしてください。
そもそもなんのための変更かというと、これによりイベントハンドラ内で拡大縮小・スクロール動作が抑止されないことがあらかじめわかるため、スクロール動作のレスポンスがかなり改善されるのです。
では、どうすれば以前のような動作にできるかというと、MDNにヒントがあります。一番下の方の「passive なリスナーを用いたスクロールのパフォーマンスの改善」というところです。よく見るとChromeも55からtouchstart
およびtouchmove
のデフォルト動作がpassive
になってますね。
ちょっとコードを抜粋して手を加えると、このような感じです。現在、ほぼすべてのブラウザがaddEventListener()
の第3引数にpassive
プロパティを含むオブジェクトを受け付けるようにはなっているのですが、互換性を重視するなら下記のような検出コードを使うとよいと思います。
/* "passive" が使えるかどうかを検出 */
var passiveSupported = false;
try {
document.addEventListener("test", null, Object.defineProperty({}, "passive", {
get: function() {
passiveSupported = true;
}
}));
} catch(err) {}
/* リスナーを登録 */
document.addEventListener('touchstart', function listener(e) {
/* do something */
e.preventDefault();
}, passiveSupported ? { passive: false } : false);
document.addEventListener('touchmove', function listener(e) {
/* do something */
e.preventDefault();
}, passiveSupported ? { passive: false } : false);
最後におまけ。こちらのページにも上記の修正を入れておきました。