CSSアニメーションのスマホ(iOS/Android)で起きる不具合/バグ

普段のスマホのコーディングでは、かなり多くのユーザーを抱えた Web 開発に携わらせて頂いていますが、 そうすると CSS アニメーションで「動かない」などの声を頂くことも多くあり、 どんな書き方をすると、どの端末で動かない事があるのか。 またその対応策など、自分の遭遇したもの中心に記載しておこうと思います。

transform が含まれた複数プロパティをアニメーションさせた時に Android2 系で動かない

「transform + 任意のプロパティ」をアニメーションさせた時に、 keyframes の指定が 0% → 100% となっていると Android2 系で動きません。 よくある要件としては、「移動させながらopacityを1にする」「縮小しながらopacityを0にする」などでしょうか。

動かない書き方

.test {
  -webkit-animation: hoge 2s infinite;
}
@-webkit-keyframes hoge {
  0% {
    -webkit-transform: translate(0, 0);
    opacity: 0;
  }
  100% {
    -webkit-transform: translate(100px, 0);
    opacity: 1;
  }
}

結論、「100%」→「99.9%,to」と書くと動きますので、 keyframes の指定は「99.9%,to」の書き方で統一しておくと良さそうです。

動く書き方

@-webkit-keyframes hoge {
  0% {
    -webkit-transform: translate(0, 0);
    opacity: 0;
  }
  99.9%,
  to {
    -webkit-transform: translate(100px, 0);
    opacity: 1;
  }
}

translate だけのアニメーションでは、「100%」にしても不具合は発生しませんでしたので、 組み合わせてアニメーションした場合に限り発生するようです。

animation-fill-mode を指定すると、Android2 系で動かない

animation-fill-modeは、Android2 系がサポートされておらず、 指定するとアニメーションが動かなくなります。

動かない書き方

.box {
  width: 50px;
  height: 50px;
  background: black;
  -webkit-animation: hoge 1s forwards;
}
@-webkit-keyframes hoge {
  0% {
    opacity: 1;
  }
  99.9%,
  to {
    opacity: 0;
  }
}

この場合、opacityの初期値を予め設定しておきforwardsを削除する事で Android2 系でも動くようになります。 初めforwardsをサポートしてないブラウザは、単に無視されるのかなと思いましたが、指定されていただけで動かなくなりましたので、指定は削除する必要がありそうでした。

動く書き方

.box {
  width: 50px;
  height: 50px;
  background: black;
  opacity: 0;
  -webkit-animation: hoge 1s;
}

擬似要素へのアニメーションが Android2,4 系で動かない

:before:after の擬似要素ですが、PC や iOS は動作確認出来ましたが、Android2,4 系でも動かない端末がありました。 現状、擬似要素へのアニメーションが必要な場合は、spanタグなどで代替する必要がありそうです。

transform の値を省略すると動かない

例えば、「拡大しながら右にアニメーションしたい」という時に、 下記のように 0%時のtranslateX()を省略すると、Chrome などでも動作確認出来ませんでした。

動かない書き方

.box {
  width: 50px;
  height: 50px;
  background-color: black;
  -webkit-animation: hoge 1s infinite;
}
@-webkit-keyframes hoge {
  0% {
    -webkit-transform: scale(0);
  }
  99.9%,
  to {
    -webkit-transform: scale(1) translateX(100px);
  }
}

0%時のプロパティも省略せずに記述することで回避できます。

動く書き方

.box {
  width: 50px;
  height: 50px;
  background-color: black;
  -webkit-animation: hoge 1s infinite;
}
@-webkit-keyframes hoge {
  0% {
    -webkit-transform: scale(0) translateX(0);
  }
  99.9%,
  to {
    -webkit-transform: scale(1) translateX(100px);
  }
}

animation-timing-function: steps()は Android2 系がサポートしていない

コマ送りのスプライト画像を準備して、animation-timing-functionのsteps()を使ってコマ送りアニメーションを実装する方法がありますが、Android2 系でサポートしていない為、もし実装したい場合は JavaScript などを使用する必要がありそうです。

steps()の実装方法については下記が参考になります。 CSS スプライトと steps を使ってアニメーション画像を作ろう

.box {
  width: 50px;
  height: 50px;
  background-image: url(../images/heart.png);
  background-size: 1450px auto;
  -webkit-animation: hoge 1s steps(29) infinite;
}
@-webkit-keyframes hoge {
  0% {
    background-position: 0 0;
  }
  99.9%,
  to {
    background-position: -1450px 0;
  }
}

また、animation-timing-functioncubic-bezier()も Android2,4 系で対応してない端末があるため(5 系から正常に動作。)、スマホ案件では、ease-in-outlinearなどの基本指定に留めておいたほうがよさそうです..。PC では問題なさそうです。

dispalay と opacity を transition させる時の注意点

例えば、最初はdisplay: none;な要素を、class 付与のタイミングでdisplay: block;にすると同時にフェードインさせたい時に、transitionでアニメーションさせる場合の注意点です。

下記のように実装すれば問題なさそうに見えますが、一瞬で box が表示されてしまい意図した動作にならない場合があります。

意図しない動作の書き方

.box {
  width: 50px;
  height: 50px;
  background: black;
  display: none;
  opacity: 0;
  -webkit-transition: opacity 1s;
}

.box.active {
  display: block;
  opacity: 1;
}

こちらの対応策はいくつか方法がありそうですが、 簡単なものだとanimationkeyframesを使用することで回避出来ます。

意図した動作の書き方

.box {
  width: 50px;
  height: 50px;
  background: black;
  display: none;
}

.box.active {
  display: block;
  opacity: 1;
  -webkit-animation: hoge 1s;
}
@-webkit-keyframes hoge {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

translate と rotate を同時にアニメーションさせる場合は、記述順で動作が異なる

こちら色々な端末で確認できましたが、 例えば、回転させながら横に動かしたい時に、 下記のような実装をすると意図した動作となります。

.box {
  width: 100px;
  height: 100px;
  background-color: black;
  -webkit-animation: hoge 1s infinite;
}
@-webkit-keyframes hoge {
  0% {
    -webkit-transform: translateX(0) rotate(0);
  }
  99.9%,
  to {
    -webkit-transform: translateX(100px) rotate(180deg);
  }
}

ただ、下記のようにrotateプロパティを先に記述してしまうと基準点が変わってしまい、 意図した動作になりませんでした。

@-webkit-keyframes hoge {
  0% {
    -webkit-transform: rotate(0) translateX(0);
  }
  99.9%,
  to {
    -webkit-transform: rotate(180deg) translateX(100px);
  }
}

translaterotateを組み合わせる場合は、translateを先に書くようにしておくと良さそうです。

3d 系の指定をすると、Android2 系でおかしな挙動となる場合がある

端末の GPU を使用する為に、下記のようなプロパティを指定する事があるのですが

  • transform: translate3d(0,0,0);
  • transform: translateZ(0);
  • backface-visibility: hidden;

指定すると Android2 系でいくつか不具合があがっているようです。

  • フォーム関連要素がページ内にあった場合に、タップ位置が変わったりといった挙動がおかしくなる。
  • scale, rotate, skew の transform プロパティが正常に動作しなくなる。
  • 特定の CSS プロパティの上書きが反映されなくなる (position など)

JIRA などで Android2 系のみおかしな挙動が起きた場合は、 ページ内の 3d 系プロパティを疑うのも 1 つの解決方法となりそうです。

使用できるプロパティ、使用出来ないプロパティを調べてみたところ…

transform 系以外のプロパティは、Android2 系でも問題なく動作していそうです。 例: opacity, background-color, box-shadowなど。

transform 系で Android2 系も含めて動作確認出来たのは、 translateX, translateY, scaleX, scaleY, rotate, skewX, skewY, transform-origin, matrixなどでした。

Android2 系で、動作確認出来なかったプロパティは、 translateZ, rotateX, rotateY, perspectiveなどの 3d 系プロパティが使えないようです。

Android2 系までサポートしようとすると 3D 系のアニメーションは厳しい状況です。

2D のアニメーションであれば Android2 系含めて動作しますので(複雑でたくさん動かしたい場合はパフォーマンス的に厳しそうですが..)、様々な場面で検討していくと良さそうです。

不具合への対策まとめ

keyframes「100%」→「99.9%, to」と書く。 ・animation-fill-mode(both や forwards など)は、Android2 系で使えないので注意する。初期値を予め設定しておくと良い。 ・cubic-bezier()は、Android2 系では使えない。 ・疑似要素でアニメーションが必要な場合は、spanタグなどで代替する。 ・keyframes内の、各%のプロパティは省略せずに記述する。 ・animation-timing-function: steps();は Android2 系がサポートしていない ・dispalayopacityを併用したアニメーションでは、keyframesを使用すると良い。 ・translaterotateを同時にアニメーションさせる場合は、translateを先に記述する。 ・3d 系のプロパティ(translate3d, backface-visibility: hidden;)の指定がある場合は、Android2 系で不具合が起きる可能性があることを知っておく。

以上になります。 間違ってるところなどありましたら、ご連絡頂けると有難いです。 今後の CSS アニメーション実装の参考にして頂ければと思います。