Reactで表現するUIモーションデザイン【デュレーション & イージング】

近年UIの表現方法の進化によって、単純にUIのレイアウトや配色・大きさが整っていてわかりやすいだけでなく、モーション（アニメーション・マイクロインタラクション）によるユーザーサポートがUX向上に大きな影響を与えるようになってきました。

Webやアプリは単に「閲覧する」だけでなく「使うための道具」として、より物理的に存在する道具と同等に高度な機能を求められているということだと思います。

その一方でUIモーションはユーザーのUXを著しく害する要因にもなっており、「不用意にモーション（アニメーション）はアプリに組み込むべきではない」と`Apple - Human Interface Guidelines`に記載があるとともに、`GoogleのMaterial Design`でもベストプラクティスについての記述が事細かにドキュメント化されています。

> [Apple - Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/
)
> [Google - Material Design Understanding motion](https://material.io/design/motion/understanding-motion.html#principles
)

つまり、UIにモーションの付与するということは毒にも薬にもなる諸刃の刃になるといったところでしょうか。
その用法を知らずに乱用してはUXを向上されるどころか、逆効果になってしまいます。
そのため、**「モーションデザイン」**についての知見を身につける必要があります。

今回は、モーションデザインを学ぶ一環として、モーションをコントロールするための基礎ともいえる、**「デュレーション」**と**「イージング」**について、Reactのパッケージ`react-spring`と`d3-ease`を活用しながらどういった手法があるのかまとめてみました。

## <font color="LightSeaGreen">そもそも 「デュレーション」 ・ 「イージング」 とは？</font>

簡単な説明を行います。
詳しくはGoogleのMaterial DesignのSpeedを読んで頂いたほうが深く理解できると思います。

> https://material.io/design/motion/speed.html#controlling-speed

### デュレーション

モーションの「継続時間」のことです。
すべてのモーションに対して同一のデュレーションを設定するのではなく、ユーザーの行ったアクションにより時間を変更することにより、よりスムーズな使用感を得ることができます。
（例えば、ページを「開く」動作よりページを「閉じる」動作を高速にするなどが該当します。）

### イージング

モーションの「変化の緩急」のことです。
直線的な動きは機械を連想させ、ユーザーに不自然な違和感を感じさせてしまいます。
自然界には「重力」「摩擦」「空気抵抗」などが存在し、動きの変化は常に一定になることはありません。
適切にイージングを使い、より自然界の動きに近い動作の緩急を再現することでより自然な使用感を実現できます。


## <font color="LightSeaGreen">PlayGround</font>

codesandboxにて簡単な位置移動のイージングを試せるデモページを用意しました。

configの`duration`・`easing`（イージング手法については後述します）を変更すると速度・速度の変化を体感することができると思います。
モーションは`react-spring`の一番基礎的な`hooks API`である`useSpring`を使っています。
（`useSpring`だけでも多彩なモーションを実現することが可能です。）

また、後述するイージングデモではx軸方向にしか位置変動させていませんが、
y軸・z軸・スケールを変動させてみると面白いモーションを実現することができます。

### デモページ

https://codesandbox.io/s/easing-96y0h?file=/src/App.tsx

### コード

```typescript:App.tsx
import React, { FC } from "react";
import { useSpring, animated } from "react-spring";
import * as easings from "d3-ease";

const App: FC = () => {
  const { xyzs } = useSpring({
    from: { xyzs: [0, 100, 0, 1] },
    xyzs: [300, 100, 0, 1],
    loop: true,
    // configでパラメータを変化させることで動作が変化する
    config: {
      duration: 2000,
      easing: easings.easeLinear
    }
  });

  return (
    <animated.svg
      style={{
        transform: xyzs.to(
          (x, y, z, s) => `translate3d(${x}px, ${y}px, ${z}px) scale(${s})`
        )
      }}
    >
      <circle cx="100" cy="50" r="40" fill="black" />
    </animated.svg>
  );
};

export default App;

```

### react-spring公式ドキュメント

> https://www.react-spring.io/


## <font color="LightSeaGreen">イージング手法</font>

もちろん手動でイージング数値を指定しても良いのですが、`react-spring`と`d3-ease`組み合わせて使うと、予め用意された関数から深く考えることなくイージング手法を試すことができます。

> https://www.npmjs.com/package/d3-ease


イージンググラフによる変化量と実際の動きを対比させながら、手法を検証していきます。
（イージンググラフは https://observablehq.com/@d3/easing からの引用です。）


### easeLinear(t)

イージングなし。

![277f1813d934ba736de90ed291ade5cc.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/fa7aa98a-a3a8-c652-83c5-aa46bb080ce6.gif)

![easeLinear.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/903743a2-4ece-7d03-c6ce-304c627f2237.png)

### easeQuadIn(t)

![ezgif.com-gif-maker.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/2b6dc1b6-ac89-c074-ad5e-50aead97b7bd.gif)


![easeQuadIn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/a71d2e1f-7c1d-3454-e3b2-e6bbf6563c8c.png)

### easeQuadOut(t)

![VayUVlaUpkUHiYv8YBxM1607212254-1607212461.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/168cab51-0e9e-a2f3-4681-17f0a6ed3b88.gif)


![easeQuadOut.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/6f23976b-be78-6dd6-8e1d-790586c5af3c.png)


### easeQuadInOut(t)

![iYkv0kPEurToOW3dBXrf1607212684-1607212756.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/3ac07eb1-de84-92cb-886f-4fee140e5a39.gif)


![easeQuadInOut(t) .png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/20c0c074-f6e9-4c67-bfbb-18a94dd3bdba.png)



### easeExpIn(t)

![EkhJGJnsNOZj2IH8UPZj1607213008-1607213149.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/3d8de3b3-a4c8-6279-3e59-5bdb2571e81e.gif)

![easeExpIn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/84fe5ac4-6fa8-734c-98a1-20a978181a6e.png)



### easeExpOut(t)

![P2ulK5hk40qtvfNpU1dn1607213402-1607213458.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/379ab9fc-ddfa-071e-669c-20bddcb819af.gif)


![easeExpOut.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/ed98ad01-658c-d4d2-0d0d-7dbb3782b59d.png)


### easeExpInOut(t)

![IxXEILIrppYcWHU5jW981607213817-1607213873.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/7af50611-801c-1e88-cc74-c2d3cd14784a.gif)


![easeExpInOut.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/942f6ffd-da03-b6b2-e81e-6ad15c57146e.png)


### easeCircleIn(t)

![HCxSGld6PxQnUHVNMQHl1607214043-1607214096.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/e184464d-206d-628c-3db2-350db7e3d1d0.gif)


![easeCircleIn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/95975b0e-8955-b1b4-481d-a579822a54db.png)


### easeCircleOut(t)

![WWkbZQG6NdldlTu0s9Z11607214323-1607214432.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/8f663322-5205-45f1-4072-b94333316134.gif)


![easeCircleOut.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/1308a8d1-7f43-720d-fb93-de875d0ece98.png)


### easeCircleInOut(t)

![0aQw3HEbz74Iws5L18U11607214557-1607214557.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/9f7207aa-ff6c-75b1-34ae-90618f69e772.gif)


![easeCircleInOut.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/15ba23dc-2343-d7fb-efe9-6b34ef8da92f.png)


### easeBackIn.overshoot(2)(t)

![wiHXMu4aEWeNvpwhIv2c1607214802-1607214863.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/80525ef3-e1c5-8a04-026c-e5e60ee6d596.gif)


![easeBackIn.overshoot(2.01).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/d022bfd6-3a73-f8db-5fc2-bb0982897973.png)


### easeBackOut.overshoot(2)(t)

![a9KMPk1mIN34oxCHUbXd1607215017-1607215082.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/67be501e-ad3e-4277-cfce-83e40f5c660a.gif)


![easeBackOut.overshoot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/041e7a9d-4798-3625-30ae-ed194cec33f9.png)


### easeBackInOut.overshoot(2)(t)

![6UzJxckB0R18evnqVXrd1607215219-1607215292.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/becee62d-4c2c-30f0-d1ed-fd13b43d82fe.gif)


![easeBackInOut.overshoot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/c39fbc45-4a3a-e9e4-613d-f6e8c7bd977d.png)


### easeBounceIn(t)

![48MhdBeVWqcuohxIOqAh1607215438-1607215546.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/1b94104b-5e7e-239f-c63b-6ab40a880304.gif)


![easeBounceIn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/66568a93-a614-de5d-45d3-967235b8d866.png)


### easeBounceOut(t)

![1j6V7mQFfldNoXrlGptC1607215693-1607215751.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/29849c71-6ee0-6ece-094f-97025de4fe4d.gif)


![easeBounceOut.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/99c41e36-1395-3f0e-19e2-2186b76ea2df.png)

### easeBounceInOut(t)

![1i02mB81yc3yOf2M68FB1607215892-1607215947.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/25884288-c678-f21c-5b6e-af3f0f938362.gif)


![easeBounceInOut.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/498345/025d53f3-81a7-9cad-9d5b-0d6d6df16746.png)


## <font color="LightSeaGreen">モーションデザイン参考サイト</font>
デモページを見ているだけでも様々なインスピレーションが得られるサイトです。

- https://observablehq.com/
- https://tympanus.net/codrops/