Mermaid図のモバイル表示で、拡大縮小まわりの不具合を直しました。

小さい修正ですが、原因の見立てを一度外したので、失敗談として残しておきます。

起きたこと

ブログ内のMermaid図で、iPhone 16eだけ変な動きになりました。

  • + を押すと拡大ではなく縮小する
  • - を押しても縮小する
  • 2本指で広げても縮小する

単純に +- の処理が逆なら、- では拡大するはずです。

でも実際には、どの操作でも小さくなりました。

最初の見立て

最初は、iOS Safari系の resize 挙動が原因だと思いました。

iPhoneでは、アドレスバーや下部ツールバーの表示変化で、画面回転していなくても resize が発火することがあります。

そのため、ズーム後に初期表示へ戻っているのではないかと考えました。

そこで、resize のたびに無条件で再フィットする処理をやめました。

幅が変わったときだけ再フィットし、高さだけの変化ではズーム状態を維持するようにしました。

それでも直らなかった

PlaywrightのChromiumで、iPhone Safari相当のUAを使った確認では正常に見えました。

しかし、実機のiPhone 16eでは直っていませんでした。

ここが今回の失敗です。

UAをiPhone Safari風にしても、実際のiOS Safariの確認にはなりません。

特に、画像サイズ、SVG、タッチ操作、viewportまわりは、ChromiumとiOS Safariで差が出ます。

本当の原因

問題は、ズーム計算の基準サイズでした。

当初の実装では、ズームするたびに画像サイズを取り直していました。

const getImageSize = () => ({
  width: sourceImage.naturalWidth || sourceImage.width || 1,
  height: sourceImage.naturalHeight || sourceImage.height || 1,
});

そして、取得した幅にズーム倍率をかけていました。

sourceImage.style.width = `${width * currentZoom}px`;

このとき、iOS Safari側で sourceImage.width が現在表示中の縮小済みサイズとして扱われると、ズームの基準が壊れます。

本来は、元のSVG幅を基準にして、そこへ倍率をかけるべきです。

しかし、現在の表示幅を基準にしてしまうと、+ でも - でもピンチアウトでも小さくなります。

つまり、ズーム方向が逆だったのではなく、ズーム計算の基準値が壊れていました。

最終的な修正

Mermaid画像ごとに、基準サイズを一度だけ固定するようにしました。

  • baseImageWidth
  • baseImageHeight

この2つを画像ロード後に確定し、ズーム操作中は更新しません。

ズーム時は、常に固定した基準サイズから計算します。

sourceImage.style.width = `${baseImageWidth * currentZoom}px`;

これで、現在の表示幅を再利用してしまう問題を避けられました。

その後、iPhone 16e実機で確認したところ、+-、2本指ピンチが正常に動くようになりました。

学び

  • +- の両方で縮小するなら、単純な符号反転ではない
  • ズーム処理では、基準サイズを最初に固定した方が安全
  • img.width は自然サイズではなく、現在の表示サイズになり得る
  • iPhone Safari系の不具合は、ChromiumのUA偽装だけでは確認不足
  • 実機確認は、最後の確認ではなく原因特定の材料になる

小さいUI不具合でも、ブラウザ差分が絡むと見立てを外します。

今回は、実機で「まだ直っていない」と確認できたことで、原因を絞り直せました。