iPhoneでanimation-delayがうまく動かない時の対処法

この記事は最終更新から4年以上が経過しています。情報が古くなっている可能性があります。

こんにちは、しばです。
前回に引き続きCSSのアニメーション問題です。
今回はiPhoneブラウザ(safari/chrome両方)で、animation-delayでアニメーションを遅延させているはずの要素が、おかしなタイミングでアニメーションするという事象に遭遇しました。

英語ではちらほら事例がありましたが日本語では見つからなかったので、英語事例も紹介しつつ対処法をまとめていきたいと思います。

animation-delayの遅延制御が効かない

今回起きたのはこういうシチュエーションでした。

3つ要素があり、この要素たちがスクロール領域に入ったら、順番にその場で回転(roate(0deg)→roate(360deg))するというものです。
今回はwow.jsを使用していたので、3つの要素にそれぞれdata-wow-durationで0.5s、1s、1.5sと指定していました。このdata属性はwow.jsによってcssのanimation-delayに使われる値です。

Win、Mac各ブラウザでは問題なく動作を確認。(firefoxは一部別の問題がありましたが)
しかし、iPhoneだけ2番目と3番目の要素が同時に動いたり、3番目の要素が一番最初に動いたりとおかしな挙動をしていました。

不具合の事例を調べて見る

まずwow.jsを外して、animation-delayの値を直接cssに記述してみましたが、相変わらずiPhoneではおかしな挙動のまま。
jsは関係なく、cssアニメーションに関連しそうです。

  1. 処理が重くてanimation-delayの解釈がうまくいってない
  2. 親要素等にかかってるcssの影響

等アタリをつけつつ、iPhoneでのanimation-delayに関する挙動を調べて見ました。

すると、2件の対処法がヒット。

1. Animation-delay not working properly on iOS, why?

Solved it. Works properly if set negative value to animation delay. For each “:nth-of-type(n)” as follows:

(翻訳)それを解決した。 負の値をアニメーション遅延に設定すると正しく動作します。 それぞれの “:nth-of-type(n)”の場合、次のようになります。

animation-delay: -9.8s;
animation-delay: -11.8s;
animation-delay: -13.8s;
animation-delay: -15.8s;
animation-delay: -17.8s;

2. animation-delay in IOS not working correctly

Looks like the issues was related to when the css was applied to the dom. To get around this Instead of using the same animation with a different delay for each elem, I used a keyframe animation for each elem that useds a keyframe offset. This allowed the dom to apply the animations and not have to worry about delay.

(翻訳)cssがDOMに適用されたときの問題のように見えます。 これを回避するには各elemの遅延が異なる同じアニメーションを使用する代わりに、1つの要素ごとに1つのkeyframeアニメーションを使用しました。これにより、DOMはアニメーションを適用することができ、遅延を心配する必要はありません。 

@-ms-keyframes bounce_circularG1 {
  0% {
    -ms-transform:scale(1)
  }
  40%, 100% {
    -ms-transform:scale(.3)
  }
}
@-ms-keyframes bounce_circularG2 {
  13.5% {
    -ms-transform:scale(1)
  }
  0%, 12.5%, 43.5%, 100% {
    -ms-transform:scale(.3)
  }
}
@-ms-keyframes bounce_circularG3 {
  26% {
    -ms-transform:scale(1)
  }
  0%,25%, 46%, 100% {
    -ms-transform:scale(.3)
  }
}
/*以下bounce_circularG4〜bounce_circularG8省略*/

どうやら、animation -delayに負の値を設定するか、要素ごとにkeyframeを用意する(animation -delayは使わない)と解決したようです。

実際に今回の例で試してみます。

1. animation-delayに負の値を設定する

animation-delayは負数にするとその分遡って実行されます。(0sは表示されるタイミングで実行)
そのため、スピナー等アニメーション回数が無限であれば問題ないですが、アニメーションを1回きりで実行する場合はアニメーション回数とanimation-delayの値をうまく調整する必要があります。

微調整しつつやってみたんですが、一見解消と思いきや、読み込みが遅い時はやはり2番目と3番目の間隔が不定、もしくは前後入れ替わってアニメーションしてしまいました。

2. 要素ごとにkeyframeを用意する

要素それぞれにkeyframeを用意します。挙動が怪しいanimation-delayを使わず、animation-durationを本来の3倍にし、keyframeの処理をこれまでの1/3の間で終わらすように記述しました。
(keyframe1→0%〜33.3%、keyframe2→33.3%〜66.6%、keyframe3→66.6%〜100%)

結果、こちらも時々あやしい挙動が残り、問題解消には至らず。

実際に対処した方法

もはやcssのアニメーションタイミングが信じられなくなってきたため、jsで処理開始を決める方法をとりました。

wow.jsではその要素に指定していたアニメーションクラスを、スクロール領域に入ったタイミングで付与することでアニメーションを開始させる仕様になっています。
それを利用して、以下のようにカスタム。

1. HTMLで、アニメーションさせる3つの要素の内最初の要素にだけwow.jsで動くようクラス名を指定。

<ul>
  <li class="circle wow flip"></li><!--wow.jsでアニメーションする要素-->
  <li class="circle"></li>
  <li class="circle"></li>
</ul>

2.wow.js内のアニメーションのクラスを付与する処理の際、特定の要素の時だけある関数(3.で定義)を呼び出すように追記。

if(t.className.indexOf("circle")){ //tはアニメーション対象の要素が入っている
	$(document).trigger('customAnimation',t);
}

3. 呼び出された関数で、2番目の要素、3番目の要素にアニメーションクラスを付与。

 $(document).on('customAnimation', function(e,data) {
    $('.circle').eq(1).addClass('flip2');
    $('.circle').eq(2).addClass('flip3');
})

念のため前述「2. 要素ごとにkeyframeを用意する」のように、要素ごとにkeyframe(今回はflip、flip2、flip3)を用意して、タイミングもそちらの%で調整していました。

このようにしたところ、無事iPhoneでもcssのanimation-delayの挙動に惑わされず、ちゃんと順番に遅延処理が行われるようになりました。

まとめ

原因については、どの記事でもiPhoneの処理スピードが関係あるんじゃないか?GPU処理の問題では?という説は上がりつつもはっきりしたことはわかっていないようでした。iPhoneの不具合という認識が強そうですね。
今回のように単純な追記で済むようなら、jsをいじってしまった方が早くて確実かと思います。

それにしても、「iPhoneならアニメーション系の動作はきっと大丈夫だろう!」みたいな認識があったので、今回の事象はびっくりしました。
前回のFirefoxに続き、cssアニメーション周りはちゃんと表示確認しなきゃな…と実感した次第です。

以上、iPhoneでanimation-delayがうまく動かない時の対処法でした!