見出し画像

iPhoneの描画遅延のせいで1週間くらい悩んだ話

👆これはTwitterに書き散らかしてたやつ

スマホ用のWebアプリで、中身がファーストビューにすっぽり収まるときに画面の高さを固定したい、みたいなのは既に出尽くされた話題で、解決方法もいくつかあって好きなの使えばええんちゃう?という感じなんですけども…

今回はよくある「JS使ってCSSのカスタムプロパティに動的に取得した値ぶっこんでcalcで計算する」やつをNext.jsで使ってみたところ、何故かiPhoneだけ値がおかしいね?ということで1週間くらい調べまくってやっと解決した話をします。

鋭意開発中はほとんどPC版Chromeで見てるので気づかなかったんですけど、QA直前でiOSシミュレータで確認したら、iPhoneシミュレータで見てる時だけ—vhに入ってる値が変なことに気づくワイ。

まあ他にもiOS Safariだけグリッドの子要素の画像が見えないとかラジオボタンの残骸が見えてるとか色々変なとこはあったんですが、それはすぐ直せたのでここでは割愛します。

本題。
iOSだけおかしいので「iOS innerHeight バグ」とかあれこれワードを変えて検索をかけるも一向にヒットしない。
特に改修期限が決まってるわけではなかったけど、QAから指摘が来る前にどうにかしたいワイ、焦る(他人から指摘されるのが死ぬほど嫌いな偏屈)。

多分もうみんな知ってる話だからいちいち書くまでもないんでしょうね😇

最初に書いてたコードは以下。

const setViewHeight = () => {
  const vh = window.innerHeight * 0.01;
  document.documentElement.style.setProperty('--vh', `${vh}px`);
}

let vw = window.innerWidth;
window.addEventListener('resize', () => {
  if (vw === window.innerWidth) {
    return;
  }
  vw = window.innerWidth;
  setViewHeight();
});

setViewHeight();

return () => window.removeEventListener('resize', setViewHeight);

参考まんまですがChromeではこれで問題なく動いてたので…
ちなみにiPhoneでもportrateからlandscapeにすると正しい値が取得できてるようでした(ただしリロードかけると元に戻る)。

ちなみに他の記事では

window.addEventListener('load', hogehoge);

で実行させてるのもあったので、デバッグついでに

window.addEventListener('load', function(){
  console.log('load');
  setViewHeight();
});

って書いてリロードしたら…iOSだけ見事に動かないw
まあ実はiOS、というよりiPhoneだけだったんですけどね…(後からiPadでは動いてたことが判明)

結論から言ってしまうと、iPhoneには結構前から描写遅延があることがわかっていて(理由は知らない)最短でも150ms程度、発火を遅らせる必要があるようです。

で、改修したコードが以下。

const setViewHeight = () => {
  setTimeout(() => {
    const vh = window.innerHeight * 0.01;
    document.documentElement.style.setProperty('--vh', `${vh}px`);  
  }, 200);
};
window.addEventListener("resize", setViewHeight);
setViewHeight();

return () => window.removeEventListener("resize", setViewHeight);

よく見かけるのは500msなんですが、ちょっと遅いのでギリギリで200msにしてます。
一応シミュレータ上ではこれで挙動揃ったのでめでたしめでたし。
vwの変動で発火するif文は一旦はずしました。

実機で問題がないことを祈ってます…(現在絶賛QA中)

ちなみにNext.jsではブラウザでしか実行しない記述はuseEffectに書くらしいので、これもuseEffectの中に書いてるんですが

こちらのブログで「if typeof〜で包んで実装した方が良い」と書かれてるんで最初書いてたら、エンジニアが「useEffectはbrowserでしか実行されないよ」とレビューで教えてくれたのではずしました。

知らないことばかりで勉強になります🤔

この記事が気に入ったらサポートをしてみませんか?