1
1

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.

React + TypeScript: Framer MotionのvariantsでDOMツリーに調和したアニメーションを加える

Posted at

Framer Motionは、本番環境に対応したReact用のモーションライブラリです。構文は宣言的で、複雑なアニメーションのコードも簡潔に書けます。つまり、コードベースが読みやすく、保守しやすいということです。

ライブラリの基本的な使い方については「React + TypeScript: Framer Motionでアニメーションを加える」をお読みください。本稿は、公式サイト「Animation」の「Variants」について解説します。つぎのサンプル001が、公式作例を参考にしたプルダウンメニューです。

サンプル001■React + TypeScript: Framer Motion Variants 01
https://codesandbox.io/s/react-typescript-framer-motion-variants-01-862dkh

コンポーネントごとのアニメーションは、animateプロパティにオブジェクトで与えられます。けれど、DOMツリー全体に調和したアニメーションを加えたいことがあるでしょう。そのときに用いるのがvariantsプロパティです。

ボタンのアニメーション

variantsには、アニメーションをあらかじめターゲットごとに、オブジェクト(menuButtonVariants)で定めてください。アニメーションとして参照されるのは、ターゲットのラベル(openclosed)です。

src/Button.tsx
const menuButtonVariants: Variants = {
	open: { rotate: 180 },
	closed: { rotate: 0 }
};
const ArrowSign: FC = () => {
	return (
		<motion.div
			variants={menuButtonVariants}
			transition={{ duration: 0.2 }}
		>
			<svg />
		</motion.div>
	);
};

variantsによるアニメーションは、DOMツリーの親からコントロールできます。animateプロパティの値に与えるのは、variantsに定めたアニメーションのラベルです。つぎのルートコンポーネントは、状態変数値(isOpen)に応じてvariantsのアニメーションを切り替えます。

src/App.tsx
export default function App() {
	const [isOpen, setIsOpen] = useState(false);
	const handleClick = () => setIsOpen(!isOpen);
	return (
		<motion.nav
			initial={false}
			animate={isOpen ? 'open' : 'closed'}
		>
			<Button onClick={handleClick} />
		</motion.nav>
	);
}

ボタンをクリックするたびに、ボタン上に表示された矢印(svg)が180度回転するようになりました(前掲サンプル001)。

メニューのアニメーション

メニュー項目を表示するのが<MenuItems>コンポーネントです。

src/App.tsx
export default function App() {
	const [isOpen, setIsOpen] = useState(false);

	return (
		<motion.nav
			initial={false}
			animate={isOpen ? 'open' : 'closed'}
		>

			<MenuItems isOpen={isOpen} />
		</motion.nav>
	);
}

variantsを定める基本は変わりません。変数menuVariantsのラベルopenclosedに、それぞれアニメーションのオブジェクトを与えました。なお、clipPathは、CSSのclip-pathに準じて要素の表示範囲を決めるプロパティです。

src/MenuItems.
const menuVariants: Variants = {
	open: {
		clipPath: 'inset(0% 0% 0% 0% round 10px)',
		transition: {
			type: 'spring',
			bounce: 0,
			duration: 0.7,
			delayChildren: 0.3,
			staggerChildren: 0.05
		}
	},
	closed: {
		clipPath: 'inset(10% 50% 90% 50% round 10px)',
		transition: {
			type: 'spring',
			bounce: 0,
			duration: 0.3
		}
	}
};
export const MenuItems: FC<Props> = ({ isOpen }) => {
	return (
		<motion.ul
			variants={menuVariants}
		>
			{Array.from(new Array(5), (_, index) => (
				<motion.li key={index}>
					Item {String(index + 1).padStart(2, '0')}
				</motion.li>
			))}
		</motion.ul>
	);
};

メニューボタンクリックで、メニューが開いたり閉じたりするようになりました(前掲サンプル001)。

メニュー項目にも、variantsで簡単なアニメーションを加えましょう(itemVariants)。垂直方向の移動と透明度の変化です(前掲サンプル001)。これで、プルダウンメニューの作例ができ上がりました。

src/
const itemVariants: Variants = {
	open: {
		opacity: 1,
		y: 0,
		transition: { type: 'spring', stiffness: 300, damping: 24 }
	},
	closed: { opacity: 0, y: 20, transition: { duration: 0.2 } }
};

export const MenuItems: FC<Props> = ({ isOpen }) => {
	return (
		<motion.ul
			variants={menuVariants}
		>
			{Array.from(new Array(5), (_, index) => (
				// <motion.li key={index}>
				<motion.li variants={itemVariants} key={index}>
					Item {String(index + 1).padStart(2, '0')}
				</motion.li>
			))}
		</motion.ul>
	);
};

今回ご紹介したのは、variantsはコンポーネントごとに定め、animateプロパティに与えるラベルを親のルートコンポーネント(App)で切り替えるという仕組みでした。variantsにはほかにも応用できる手法があります。詳しくは、公式サイトの「Variants」をご参照ください。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?