拡大縮小・スクロールを無効にする方法がiOS 11.3 Safariで効かなくなった問題と解決法

その昔、スマホ向けWebページの拡大縮小・スクロールをさせたくない場合にはviewportuser-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

具体的にどんな変更があったかというと、bodydocumentwindowtouchstartおよび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);

最後におまけ。こちらのページにも上記の修正を入れておきました。