🌸

Spring AnimationとVanilla JSで始めるFramer Motion

2024/11/15に公開1

2024 年 11 月 14 日に Framer Motion の作者である、Matt Perry さんが作成した、Motion One と Framer Motion が統合され、Motion という名前になりました。

統合されたことにより、実質 Framer Motion が Vanilla JS や Vue などのフレームワークに依存しないライブラリとして使えるようになりました。

https://motion.dev/blog/framer-motion-is-now-independent-introducing-motion

この記事では、Spring Animation と Vanilla JS で Motion を使う方法を紹介します。

Spring Animation の説明を飛ばして、Motion の使い方を知りたい方はMotion を使うからご覧ください。

Framer Motion と Motion One とは?

Spring Animation や Keyframe Animation など、CSS では表現しにくかったアニメーションを実装することができるライブラリです。

Framer Motion は、React を前提としたライブラリでしたが、Motion One は Vanilla JS や Vue などのフレームワークに依存しないライブラリとして作成されました。

これらが統一され、Motion OneMotionに、Framer MotionMotion for Reactになりました。

https://github.com/motiondivision/motion

Spring Animation について

iPhone の Dynamic Island などの弾むようなアニメーションがSpring Animationです。

Spring Animationの説明だけで 1 つの記事が書けるほどの内容なので、かなり割愛して説明します。

CSS Animation との違い

Spring Animationは、現実世界のバネのような流動的で自然な物理法則に基づいたアニメーションを表現することができるアニメーションです。

CSS Animationでは、キーフレームとアニメーションの時間を用いた、ベジェ曲線のようなアニメーションしか表現できませんでした。

Spring Animationでは、stiffness(バネの強さ)damping(バネの減衰)mass(バネの質量)という 3 つのパラメータを用いて、オブジェクト的にアニメーションを表現することができます。

<motion.div
  transition={{
    type: "spring", // アニメーションのタイプを指定
    stiffness: 100, // バネの強さ
    damping: 10, // バネの減衰
    mass: 1, // バネの質量
  }}
/>

Apple が考えた Spring Animation

上記で説明したように、Spring Animationは、stiffness(バネの強さ)damping(バネの減衰)mass(バネの質量)という 3 つのパラメータを用いて、表現することができます。

ですが、これらは直感的ではないため、Applebounce(弾む力)duration(アニメーションの時間)という 2 つのパラメータを用いて、スプリングアニメーションを表現する方法を考えました。

<motion.div
  transition={{
    type: "spring", // アニメーションのタイプを指定
    bounce: 0.15, // 弾む力
    duration: 0.75, // アニメーションの時間
  }}
/>

3つのパラメーターと2つのパラメーターは、それぞれ数式で変換することができます。
詳しくは以下の動画をご覧ください。

https://developer.apple.com/jp/videos/play/wwdc2023/10158/

Spring Animation はなにが良いのか?

Spring Animationは、バネのように弾むだけが良いポイントではありません。
以下の 3 つのポイントが良いと言われています。

  1. アニメーションの終了が非常に自然である
  2. アニメーションを不自然な動きをすることなく、中断することができ、次にアニメーションにスムーズに移行することができる。
  3. 現実世界の物理法則に基づいたアニメーションを表現することができる
bounce にはどの値を設定すればいいのか?

まず、bounceは 0 から 1 の値を設定することができます。

基本的には、0 を設定しましょう。Spring Animationは上記で述べたように、弾むのが利点ではないです。
すこし弾むようにしたい場合は、最大でも 0.15 を設定しましょう。
(個人の感覚です)

これ以上書くと記事の本質からずれてしまうため、機会があれば別の記事で詳しく書きたいと思っています。

Motion を使う

https://motion.dev/docs

インストール

npm install motion

なんと、motion/miniは脅威の 2.5kb です。GSAP 同等品よりも 90%小さいそうです。

Tiny
Mini animate is 90% smaller than its GSAP equivalent, scroll 75% smaller.

基本的な使い方

animateを import し、そこに対象の要素とアニメーションを指定します。

import { animate } from "motion";

const target = document.querySelector(".target");

// 360度回転する
animate(target, { rotate: 360 });

target には、セレクターを指定することもできます。

animate(".target", { rotate: 360 });

Spring Animation の実装

animateの第三引数にtransitionを指定することで、Spring Animation を実装することができます。

import { animate } from "motion";

const target = document.querySelector(".target");

// 1秒かけて360度回転する
animate(target, { rotate: 360 }, { type: "spring", bounce: 0, duration: 1 });
Spring なし Spring あり 同時実行
Springなし Springあり

Springを指定したほうが、より自然なアニメーションになります。
特にアニメーションの終了部分が自然に見えます。

Springを指定しない挙動は、不自然で、少し物理的な挙動とは違うものになります。

Springを少し強くしてみましょう。

animate(target, { rotate: 360 }, { type: "spring", bounce: 0.15, duration: 1 });
Spring なし Spring 強め 同時実行

若干強くなったので、終わり部分がバネのように弾むようになりました。

FadeIn アニメーション

画面に表示された時に、フェードインするアニメーションを実装します。

inViewを使うことで、画面に表示された時にアニメーションを実行することができます。

import { inView } from "motion";

inView(
  ".fadein",
  (info) => {
    animate(
      info.target,
      { opacity: 1, y: [32, 0] },
      { duration: 0.75, type: "spring", bounce: 0 }
    );
  },
  { margin: "0px 0px -120px 0px" }
);

上記のように、第1引数にターゲットとなる要素を指定し、第2引数に画面内に入った時に実行する関数を指定します。

関数の中では、animateを使って、アニメーションを指定します。

FadeIn

yには、[32, 0]のように、動きをつけるとより自然なアニメーションになります。

弱めのblurをつけるのも良いかもしれないです。

animate(
  info.target,
  { opacity: 1, y: [32, 0], filter: ["blur(4px)", "blur(0px)"] },
  { duration: 0.75, type: "spring", bounce: 0 }
);

inViewの詳細は以下のドキュメントをご覧ください。

https://motion.dev/docs/inview

スクロールアニメーション

指定した要素がスクロールされた時に、アニメーションを実行することができます。

import { scroll } from "motion";

scroll((progress) => console.log(progress));

progressは、0 から 1 のスクロール量が返されます。
1 の場合は、スクロールが完了したことを表します。

transformを使う

スクロール量に応じて、アニメーションを実行する場合は、transformを組み合わせて実装します。
transformは、Motionからexportされている、Utilsです。

この関数は、入力値を別の出力に変換してくれます。便利です。

import { transform } from "motion";

const transformNumber = transform(
  [0, 1], // Input
  [0, 100] // Output
);

transformNumber(0.5); // 50

上記の場合は、0 から 1 の値を、0 から 100 の数値に変換してくれます。
例えば、0.5 の場合は、50 になります。

上記の関数を使って、スクロール量に応じて、アニメーションを実行します。

試しに以下の3種類のアニメーションを実装します。

スクロールアニメーション

1. 色が変わりながら、回転するアニメーション

import { transform } from "motion";

scroll((progress, _) => {
  // 0から1のスクロール量を、0度から360度の回転量に変換
  const rotate = transform([0, 1], ["0deg", "360deg"]);

  // 0から1のスクロール量を、色の変化に変換
  const color = transform(
    [0, 0.3, 0.6, 0.9, 1],
    ["#cc0000", "#66cc00", "#00cccc", "#6600cc", "#cc00cc"]
  );

  animate(
    box,
    {
      background: color(progress),
      rotate: rotate(progress),
    },
    { type: "spring", bounce: 0.1, duration: 1 }
  );
});

入力と出力の配列の長さは、同じである必要があります。

2. 進捗度を表現するアニメーション

import { transform } from "motion";

scroll((progress, _) => {
  animate(progressCircle, {
    pathLength: progress,
  });
});

SVG の Circle を使って、進捗度を表現するアニメーションです。
Path の長さを変更することで、進捗度を表現します。

https://motion.dev/docs/react-scroll-animations#track-element-position

3. 縦スクロール量を横スクロールに変換するアニメーション

import { transform } from "motion";

scroll((progress, _) => {
  const translateX = transform([0, 1], ["0%", "-80%"]);

  animate(target, { x: translateX(progress) });
});

とても簡単ですね。

このように、スクロール量に応じて、アニメーションを実行することができます。

まとめ

新しく統合されたMotionSpring Animationを紹介しました。
紹介した以外にもたくさんのUtilsやアニメーションに適した関数があります。

Spring Animationを使うことによって、より自然なアニメーションを実装することができます。
ぜひ、Motionを使って、より自然なアニメーションを実装してみてください。

それでは良いMotionライフを!

参考

https://developer.apple.com/jp/videos/play/wwdc2023/10158/
https://blog.maximeheckel.com/posts/the-physics-behind-spring-animations/
https://motion.dev/
https://animations.dev/
https://emilkowal.ski/

GitHubで編集を提案
chot Inc. tech blog

Discussion