Cover Image for アニメーションを使いまわすための「motion+Slot」戦略

アニメーションを使いまわすための「motion+Slot」戦略

https://stitches.dev/

概要

Framer MotionをReact Slotで運用すると、アニメーションの再利用性が飛躍的に向上します。

React Slot

https://www.radix-ui.com/docs/primitives/utilities/slot

Slotは子コンポーネントにpropsを渡す役割を持ちます。

これが

<Slot color="red"><AnyComponent /></Slot>

実質的にこうなります。

<AnyComponent color="red" />

Slot本体は消えるものの、propsを介して任意のコンポーネントに機能を与えられるという点が重要です。余分なdivを生成することはありません。

詳しくはこちらを参照ください。

Framer Motion

https://www.framer.com/motion

Framerが提供しているアニメーションライブラリです。

どんなアニメーションでも、基本的にpropsだけで完結してしまうのが特徴です。

<motion.div
  drag="x"
  dragConstraints={{ left: -100, right: 100 }}
  whileHover={{ scale: 1.1 }}
  whileTap={{ scale: 0.9 }}
/>

カスタムコンポーネントはmotion関数に渡してアニメーションをつけます。

const MotionComponent = motion(Component);
...
<MotionComponent animate={{ scale: 0.5 }} />

motion+Slotで何ができる?

motionSlotを組み合わせてmotion(Slot)を作ります。すると、

motionが提供してくれる手軽でリッチなアニメーション機能をそのまま、提供元を完全に隠蔽して提供する

ことができるようになります。

何を言っているのか、私もよくわからないので具体例に移りましょう。

具体例

motion(Slot)を使って、みんな大好き「ふわっ」が手軽に実装できるようにしましょう。

実装

import React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { motion } from 'framer-motion';

const ContentLayout = motion(Slot);

type Custom = {
  y?: number;
  once?: boolean;
  amount?: number;
  duration?: number;
};

const defaultCustom: Custom = {
  y: 20,
  once: true,
  amount: 0.3,
  duration: 0.6,
};

const config = (custom?: Custom): React.ComponentProps<typeof ContentLayout> => {
  const { y, once, amount, duration } = { ...defaultCustom, ...custom };

  return {
    initial: {
      opacity: 0,
      y,
    },
    whileInView: {
      opacity: 1,
      y: 0,
    },
    viewport: {
      once,
      amount,
    },
    transition: {
      duration,
    },
  };
};

type Props = {
  children: React.ReactNode;
  custom?: Custom;
};

export const Enter = React.forwardRef<
  React.ElementRef<typeof ContentLayout>,
  Props
>(({ children, custom }, forwardedRef) => (
  <ContentLayout {...config(custom)} ref={forwardedRef}>
    {children}
  </ContentLayout>
));

Enter.displayName = 'Enter';

使い方

たったこれだけで、h1がふわっと入場します。

<Enter>
  <h1>Hello CodeSandbox</h1>
</Enter>

CodeSandboxに使用例を上げました。

@codesandbox

ぜひ、別窓で開いて余計なdivが生成されていないことをお確かめください。

注意点

motionは内部的にrefを使用します。なので、対象のコンポーネントがカスタムコンポーネントである場合、正しくrefをフォワーディングしている必要があります。

refやforwardRefをご存知ない方は、調べてみてください。きっと、Slotmotionに比べてはるかに多くの記事がヒットするでしょう。

まとめ

motion(Slot)で再利用性バツグンのアニメーションコンポーネントを作ることができました。

実装自体motionの軽い延長に過ぎないので、簡単にオリジナルのアニメーションコンポーネントが作れると思います。是非お試しください。

私も色々実装してみて、またの機会に紹介したいと思います。