iOS Safari でダブルタップによるズームを防ぐには touch-action: manipulation が一番簡単

iOS Safari で見せる Web サイトをネイティブアプリ風に見せるための演出の一つとして、適当なところでダブルタップした時にズームしないことを実現したい。

Twitter や Instagram を iOS Safari で見るとそのような動きが実現されているので、何かやり方はあるんだろうなぁーと思ったのだけど、かなり手こずってしまった。

目次

iOS10 までは user-scalable=no が使えた

meta 要素で指定するヤツ。今では動かない。

<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">

どうも iOS10 以降で、ユーザビリティを考えてズームを禁止させない方針になったため、user-scalable=no 指定は無効になった。

JavaScript で TouchEnd イベントをチェックする方法

こんなコード。

let lastTouchEndTime = 0;
document.addEventListener('touchend', (event) => {
  const now = new Date().getTime();
  if((now - lastTouchEndTime) < 350) {
    event.preventDefault();
  }
  lastTouchEndTime = now;
});

要するに「350ミリ秒以内の2回目のタップを無効化する」という処理なのだが、試していた限りどうやってもこの処理をすり抜けてしまうタイミングがあった。400ミリ秒とか700ミリ秒とか色々試したが、あまりしきい値を大きくしすぎると、今度は少し素早い操作をしていて「別々の2タップ」のつもりだったのに、後のタップ操作が無効化されてしまったりと、綺麗にいかなかった。

ちなみに、ピンチ操作を無視する JavaScript もある。

document.addEventListener('touchstart', (event) => {
  if(event.touches && event.touches.length > 1) {
    event.preventDefault();
  }
}, {
  passive: false
});

2本指以上のタップを無効化している。iOS11 あたりから passive: false 指定がないと上手く効かなくなった。

他にも、TouchEnd 時のイベントで preventDefault() しつつ、click() を呼んでやる、というようなハックも見かけたが、さすがにイマイチか。

なお、ズーム倍率は event.scale の値で確認できる。1 を等倍 (100%) として表現されるが、ReadOnly なプロパティなので、event.scale = 1 などと代入しても、倍率をリセットすることはできなかった。

この辺のスクリプトで試行錯誤したが、綺麗に実現できるモノがなかった。

touch-action: manipulation が一番カンタン

CSS で touch-action というプロパティがある。スワイプ操作に制限を与えるプロパティだ。

しかし Can I Use を見たところ、iOS Safari では automanipulation しか許容していないようだった。

touch-action で検索すると以下の日本語文献がよくヒットするのだが、イマイチ文脈が分からないので無視した。プロパティの説明としては合ってる。

で、初期値である auto を除いて、iOS Safari で唯一使える touch-action: manipulation というプロパティを調べてみたところ、

という仕様のようだ。

ということで、JavaScript は使わず、以下のコードだけでダブルタップによるズームを抑止できた。

html {
  touch-action: manipulation;
}

ちなみに、Twitter も Instagram も、ウェブサイト版はピンチズームを抑止していなかった。ピンチを抑止してしまうと、何かの拍子にズームされてしまった時に戻せなくなるので、ピンチは抑止しないでおこうと思う。