18
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Qiita株式会社Advent Calendar 2021

Day 12

QiitaのLGTMボタンにアニメーションがつくまでの流れ

Last updated at Posted at 2021-12-25

Qiita株式会社 Advent Calendar 2021(2)の12日目の担当は、CX向上グループの@xrxoxcxoxです!

この記事の概要

最近、LGTMボタンにアニメーションをつけました。
こちらをどのように実装していったのかを簡単に説明します。

全体の流れ

  1. After Effectsでアニメーションを作り、Bodymovinでjson書き出し
  2. Lottie(react-lottie)でReact上で動かす
  3. 実際に触ってみて調整

After Effectsでアニメーションを作り、Bodymovinでjson書き出し

もととなるデータを作っていきましょう。

最終出力がsvgアニメーションになるのもあって、After Effectsの全ての機能が使えるわけではありません。
以下のことに気をつけながらアニメーションを考えます。

  • シェイプレイヤーかパスデータだけで作る
  • 複雑なマスクを使わない
  • 基本、エフェクトは使えないものと考える

一旦仕上がったものはこんな感じ。

Bodymovinのインストール方法や詳しい使い方は省いてしまいましたが、@naomunaomuさんの以下の記事が分かりやすいのでおすすめです。

Lottie(react-lottie)でReact上で動かす

コード上で動かすにあたり、公式から出ているライブラリはlottie-webです。

QiitaのフロントはReactなのですが、上記のライブラリはReactには最適化されていません。
そのため、react-lottieを代わりに使いました。

react-lottieは更新がしばらくないのが気になりますが、今回はスター数の多さで選びました。
もしご自分でも試してみようと思った場合は、他のライブラリも含めて検討してみてください。

ハマった点

単に動かすだけであればLottieコンポーネントにアニメーションデータのjsonを渡すだけで良いのですが、Qiitaの場合はページに訪れた際の状態で分岐が発生します。

  1. まだLGTMしていない記事の場合
    1. ボタンは未LGTM状態(0フレーム目)
    2. クリックするとアニメーションしてLGTM済みの状態(最終フレーム)に遷移する
    3. もう1度クリックすると未LGTM状態(0フレーム目)に戻る
    4. 2と3の繰り返し
  2. 既にLGTMしている記事の場合
    1. ボタンはLGTM済みの状態(最終フレーム)
    2. クリックすると未LGTM状態(0フレーム目)に戻る
    3. もう1度クリックするとアニメーションしてLGTM済みの状態(最終フレーム)に遷移する
    4. 2と3の繰り返し

何を当たり前のことを言っているんだと思われるかもしれません。

しかし、Lottieには「任意のフレームからスタートする」機能がないのです。

そのため2の「既にLGTMしている記事」に訪れた場合に「LGTM済みの状態(最終フレーム)」で提供できません。
色々工夫してみたものの、ページに訪れる度にアニメーションしてしまうとか、変な動きになってしまいました。

ちなみにだいぶ古いですがこのようなIssueも立っていて、ハック的な方法が示されています笑1

解決した方法

まずは以下の2つの要素を用意しました。

  • LGTM済みの見た目の静的なsvg
  • アニメーションするsvg(= Lottie)

そして2つを出し分けます。
実際のコードだと色々な要素が含まれているので、簡略化して以下に載せます。

おおまかなイメージ
interface Props {
  isLiked: boolean
}

const LgtmButton = ({ isLiked }: Props) => {
  const [isAnimationWorked, setIsAnimationWorked] = useState(false)
  return (
    <button onClick={() => {setIsAnimationWorked(true)}>
      {!isAnimationWorked && isLiked ? (
        <LgtmCompleted />
      ) : (
        <Lottie
          isStopped={!isLiked}
          // その他、オプションの指定色々
        />
      )}
    </button>
  )
}
  • isLikedはグローバルに管理、isAnimationWorkedはローカルに管理
  • ページに訪れた時点ではisAnimationWorkedは絶対にfalseなため、LGTM済みかどうかで分岐する
    • LGTM済みであれば<LgtmCompleted />(LGTM済みの見た目の静的なsvg)が表示される
    • 未LGTMであれば再生前の<Lottie />が表示される
  • ボタンをクリックした時点でisAnimationWorkedがtrueになる
    • LGTM済みでボタンをクリックした場合はisLikedがfalseになるため再生前の<Lottie />に切り替わる
    • 未LGTMでボタンをクリックした場合はisLikedがtrueになるためLottieのisStoppedがfalseとなり、動き出す

かなり苦労しましたが、こういった方法で実現できました。

実際に触ってみて調整

さきほどAfter Effectsで作成したときと、実際に押せるボタンとして実装したときとではアニメーションの印象が若干違いました。

そのため、一度実装した後にタイミングやデュレーションなど少しずつ調整……。
数フレームずつ速くしたり遅くしたりを繰り返してみつつ、なんとか完成にこぎつけました。

まとめ

  • After Effectsで作ったアニメーションを簡単にjsonに書き出して使えるけど、エフェクトをつけるなど凝ったことはできないので注意
  • Lottieは最終フレームでフリーズさせる機能がないので条件分岐で実装
  • 最後は目で見て、実際に触って確認。
  1. 更にこの方法はlottie-webならできそうなもののreact-lottieではできないっぽかったです……。

18
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?