React および Web 一般におけるアニメーションは、ページ上の UI 要素の視覚的な状態を時間の経過とともに変化させるプロセスです。視覚的なステータスとは何を意味しますか? 要素の外観に影響を与えるプロパティ: 高さ、形状、他の要素との相対的な位置など。アニメーションの中心的な考え方は、ページ上の何かの特定の目に見えるプロパティを時間の経過とともに変更するということです。
React でアニメーションを作成する方法はいくつかありますが、それらはすべて、CSS ルールを適用して視覚的な状態を変更する CSS アニメーションと、JavaScript を使用して要素のプロパティを変更する JavaScript アニメーションの 2 つの大きなカテゴリに分類されます。どちらのカテゴリでも、アニメーションを最初から実装することも、ライブラリを使用することもできます。CSS に関しては、CSS ルールを使用してアニメーションを合成したり、Animate.cssなどのサードパーティ ライブラリを使用したりできます。
JavaScript の使用を選択した場合は、カスタム コードを記述してアニメーションを作成したり、GSAP や Framer Motion などのライブラリを使用したりできます。各ライブラリにはそれぞれ利点があり、アニメーションを記述する方法もライブラリごとに異なります。この記事では、Framer デザイン チームによって作成および維持されている React アニメーション ライブラリであるFramer Motionについて説明します。
すべての Framer Motion アニメーションを支えるコア コンポーネントを学び、Framer Motion を優れたツールにするいくつかの機能について洞察し、ライブラリを最大限に活用するためのベスト プラクティスを発見し、それをすべてステップで実践します。ステップごとの例。
記事ディレクトリ
始める前に、いくつかの基本的な知識が必要です。
- React、HTML、CSS、JS の知識
- コマンドラインとnpmの知識
React アニメーションの作成に Framer Motion を使用する理由は何ですか?
React プロジェクトで Framer Motion を検討する必要があるのはなぜですか? Framer Motion はかなり人気があり、積極的に維持されているライブラリであり、 Github上に 19,000 のスターがあり、それをサポートするリソースが大量にあります。
しかし最も重要なことは、Framer Motion は、複雑なプロダクション グレードのアニメーションを可能な限り少ないコードで作成できるようにするという考えに基づいて構築されていることです。Framer Motion の使用は非常に簡単で、コードを 1 行追加するだけでドラッグ アンド ドロップできます。Framer Motion は、SVG アニメーションやアニメーション レイアウト オフセットなどのタスクも大幅に簡素化します。
Framer Motion コンポーネントと API
Framer Motion はアニメーションに対する直感的なアプローチを採用しています。これは、マークアップをラップし、必要なアニメーションのタイプを指定できるように属性を受け入れるコンポーネントのセットを提供します。Framer Motion のコア コンポーネントには次のものが含まれます。
motion
コンポーネントAnimatePresence
コンポーネント
このmotion
コンポーネントはすべてのアニメーションの基礎を提供します。React コンポーネントで HTML 要素をラップし、initial
と に渡されたanimate
状態を使用してそれらをアニメーション化します。以下に例を示します。Web 上のどこにでもある通常の div を取得します。
<div>I have some content here</div>
この div をロード時にページにフェードインしたいとします。必要なのは次のコードだけです。
<motion.div
initial={
{
opacity:0 }}
animate={
{
opacity:1 }}
>
I have some content in here
</motion.div>
ページが読み込まれると、div は透明から完全に不透明までスムーズにアニメーション化し、徐々にページにフェードインします。通常、モーション コンポーネントをインストールするときは、initial
で指定した値をコンポーネントに適用し、 でanimate
指定した値に達するまでコンポーネントをアニメーション化します。
AnimatePresence
このコンポーネントは と連動し、DOMmotion
から削除する要素がページから削除される前に終了アニメーションを表示できるようにするために必要です。AnimatePresence
次の 2 つの条件のいずれかを満たす直系の子にのみ適用されます。
- 子は
motion
コンポーネントでラップされます - 子には、
motion
その子の 1 つとしてコンポーネントでラップされた要素があります。
目的の終了アニメーションは、にexit
属性を追加して指定する必要があります。motion
以下にAnimatePresence
例を示します。
<AnimatePresence>
<motion.div
exit={
{
x: "-100vh", opacity: 0 }}
>
Watch me go woosh!
</motion.div>
</AnimatePresence>
ラッピングAnimatePresence
div が DOM から削除されても、消えることはありませんが、左に 100vh スライドし、その過程で透明になります。その後のみ、div はページから削除されます。複数のコンポーネントが の直接の子である場合、 DOM 内で追跡できるkey
ように、すべてに一意の値が必要であることに注意してください。AnimatePresence
多くのアニメーションに必要なのはこれら 2 つのコンポーネントだけですが、Framer Motion にはより複雑な使用を可能にする機能があります。motion
これらの機能の 1つは、ホバリング、タップ、ページ要素のドラッグなど、エンド ユーザーによるジェスチャに応じてコンポーネントがアニメーションをトリガーできるようにする、コンポーネント上の一連のプロパティです。これらのプロパティはジェスチャと呼ばれます。これは、ホバー ジェスチャの使用法を示す簡単な例です。
<motion.div
whileHover={
{
opacity: 0
}}
>
Hover over me and I'll disappear!
</motion.div>
whileHover
プロパティはホバー ジェスチャです。上記のコードは、マウスを div の上に置くと div をフェードアウトし、マウスを離すと前の状態に戻します。
より大きな例を試す前に、最後の機能を見てみましょう。長さや遅延の調整など、アニメーションのさまざまな側面を調整したい場合、何を使用しますか? Framer Motion には、transition
これらを指定できるプロパティが用意されています。Framer Motion では、 Spring アニメーションや Tween (イージングベース) アニメーションなど、さまざまなタイプのアニメーションを選択することもでき、transition
プロパティを使用してこれを制御できます。以下に例を示します。
<motion.div
initial={
{
opacity:0 }}
animate={
{
opacity:1 }}
transition={
{
duration: 0.5, delay: 0.1 }}
>
I have some content here
</motion.div>
これは前のフェードイン アニメーションと同じですが、そのtransition
特性により、アニメーションは開始までに 0.1 秒待機し、持続時間は 0.5 秒になります。
Framer Motion を使用して React アプリケーションにアニメーションを実装する
学んだことをすべて利用して、より複雑な例を組み立ててみましょう。この記事を終えるまでに、次のようなアニメーション通知トレイが作成されるでしょう。
プロジェクトを初期化する
まず、サンプルを配置するディレクトリに移動します。次に、ターミナルを開き、次のコマンドを使用して Vite を使用してスターター React アプリケーションを作成します。
npm create vite@latest
次に、次のようにプロンプトに答えます。
次に、作成したばかりのプロジェクトに移動し、 を実行しnpm install
、さらに を実行しますnpm run dev
。プロジェクト フォルダーは次のようになっているはずです。
src/assets
フォルダーを削除しApp.css
、アニメーションを使用せずにナビゲーション トレイをコーディングします。プロジェクトの CSS から始めて、index.css
の内容を次のものに置き換えます。
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
font-weight: 400;
}
body {
margin: 0;
min-width: 320px;
min-height: 100vh;
background-color: #fff;
color: #111827;
}
header {
height: 4rem;
font-size: 1.1rem;
border-bottom: 1px solid #e5e7eb;
display: flex;
align-items: center;
padding: 0 2rem;
}
header > .header__left {
width: 50%;
font-size: 1.5rem;
}
header > .header__right {
width: 50%;
display: flex;
justify-content: flex-end;
align-items: center;
list-style-type: none;
margin: 0;
padding: 0;
}
.header__right > * {
margin: 0 1.5rem;
position: relative;
}
.header__right > .notification__button {
height: 2rem;
width: 2rem;
cursor: pointer;
}
.header__right > .notification__icon {
height: 100%;
width: 100%;
}
.header__right > .image {
border-radius: 50%;
height: 3rem;
width: 3rem;
overflow: hidden;
}
.header__right > .image > img {
height: 100%;
width: 100%;
}
.notification__tray {
border-radius: 6px;
box-shadow: 0px 0px 8px #e5e7eb;
position: fixed;
width: 24rem;
top: 4.5rem;
right: 2rem;
color: rgb(65, 65, 81);
font-size: 0.875rem;
line-height: 1.25rem;
}
.notification__tray > ul {
list-style-type: none;
margin: 0;
padding: 0;
}
.notification__tray li {
padding: 1rem 2rem;
border-bottom: 1px solid #e5e7eb;
display: flex;
align-items: center;
justify-content: space-between;
}
.notification__tray li:hover {
background-color: #e5e7eb;
color: #111827;
}
.notification__tray li .clear__button {
width: 1.5rem;
height: 1.5rem;
cursor: pointer;
}
.notification__tray li .clear__icon {
width: 100%;
height: 100%;
}
.todo__header {
text-align: center;
}
.todo__container {
list-style-type: none;
}
.todo__item {
border: 1px solid #e5e7eb;
border-radius: 5px;
box-shadow: 0px 0px 8px #e5e7eb;
color: #111827;
margin: 1.5rem auto;
width: 350px;
padding: 1.5rem 2rem;
background-color: #e5e7eb;
}
次にヘッダーのコードです。でsrc
、Header.jsx
というファイルを作成し、次の内容を入力します。
import {
useState } from "react";
import NotificationTray from "./NotificationTray";
const initialNotifications = [
"User #20 left you a like!",
"User #45 sent you a friend request",
"Your song has been uploaded!",
"Thanks for signing up!",
];
const Header = () => {
const [showNotifications, setShowNotifications] = useState(false);
const [notificationContent, setNotificationContent] =
useState(initialNotifications);
const handleDeleteNotification = (content) => {
setNotificationContent(
notificationContent.filter((item) => item !== content)
);
};
return (
<header>
<div className="header__left">Brand</div>
<ul className="header__right">
<li
className="notification__button"
onClick={
() => {
setShowNotifications(!showNotifications);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="notification__icon"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0M3.124 7.5A8.969 8.969 0 015.292 3m13.416 0a8.969 8.969 0 012.168 4.5"
/>
</svg>
</li>
<li className="image">
<img src="https://www.dummyimage.com/48x48" />
</li>
</ul>
{
showNotifications ? (
<NotificationTray
notificationContent={
notificationContent}
handleDeleteNotification={
handleDeleteNotification}
></NotificationTray>
) : null}
</header>
);
};
export default Header;
簡潔にするために、開始コードについては詳しく説明しませんが、基本的には次のことを行います。
- 通知トレイコンポーネントをインポートする
- トレイの状態を作成し、状態から通知を削除するヘルパー関数を定義します。
- ヘッダーのマークアップを作成し、条件付きでトレイをレンダリングします
次に、通知トレイ コンポーネントのコードを作成します。というファイルを作成しNotificationTray.jsx
、次のコードをその中に入れます。
const NotificationTray = ({
notificationContent,
handleDeleteNotification,
}) => {
return (
<div className="notification__tray">
<ul>
{
notificationContent.map((content) => {
return (
<li key={
content}>
<span>{
content}</span>
<span
className="clear__button"
onClick={
() => {
handleDeleteNotification(content);
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={
1.5}
stroke="currentColor"
className="clear__icon"
title="Clear notification"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</span>
</li>
);
})}
</ul>
</div>
);
};
export default NotificationTray;
このコード:
- トレイのステータスを として表示し
<ul>
、それぞれの<li>
通知を表示します。 - プログラム機能を使用して
Header.jsx
、クリアボタンがクリックされたときに通知を削除します
最後に、ヘッダーを次のようにレンダリングしますApp.jsx
。
import Header from "./Header"
function App() {
return (
<>
<Header></Header>
</>
)
}
export default App
これには、通知トレイを適切に動作させるために必要なコードがすべて含まれています。React アプリをブラウザーで表示すると、次のような Web ページが表示されるはずです。
フレーマーモーションアニメーションを追加
ベルのアイコンのアニメーションを開始します。ホバー時にトリガーされるリンギング モーションは、SVG アイコンを Z 軸に沿って最初に一方向に、次にもう一方の方向に回転し、その後通常に戻すことによって作成されます。
方法は次のとおりです:Header.jsx
上部で importmotion
とAnimatePresence
:
import {
motion, AnimatePresence} from "framer-motion";
次に、Header.jsx
SVG アニメーションを次のように追加します。
<motion.svg
whileHover={
{
rotateZ: [0, -20, 20, -20, 20, -20, 20, 0],
transition: {
duration: 0.5 },
}}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="notification__icon"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0M3.124 7.5A8.969 8.969 0 015.292 3m13.416 0a8.969 8.969 0 012.168 4.5"
/>
</motion.svg>
SVG がどのように変化するかは次のとおりです。
- それは包みます
motion
- プロパティとしてジェスチャがある
whileHover
ため、SVG ホバー時にのみアニメーションが起動します。 - に渡されるオブジェクトでは
whileHover
、配列内の値の範囲を指定します。この配列はキーフレームと呼ばれます。これは、Framer Motion がキーフレームで指定された各値に SVG をアニメーション化しないことを意味します。 - に渡されるオブジェクトでは
transition
、アニメーションを 0.5 秒継続することを指定します。
適用すると、Web ページ上にカーソルを置くとチャイム音が聞こえるはずです。
次に実装するアニメーションは、通知トレイ上のアイテムのフェードイン アニメーションです。作成しNotification.jsx
てインポートし、motion
次の操作を行いますAnimatePresence
。
import {
motion, AnimatePresence } from "framer-motion";
div
次に、外側のコードを次のように変更します。
<motion.div
className="notification__tray"
initial={
{
opacity: 0 }}
animate={
{
opacity: 1 }}
>
<ul>
.....
</ul>
</motion.div>
変更するのは、div が 0 から始まり、完全に表示されるようにアニメーション化されるように と propsの値を設定することdiv
だけです。Returnこれを繰り返し、次のように 0.2 秒の継続時間を追加します。motion.div
initial
animate
opacity
<li>
map
{
notificationContent.map((content) => {
return (
<motion.li
key={
content}
initial={
{
opacity: 0 }}
animate={
{
opacity: 1 }}
transition={
{
duration: 0.2 }}
>
...
</motion.li>
)
})
}
追加のタッチとして、各通知の終了をアニメーション化してみましょう。li
これを行うには、トレイから取り外すときにスライド アニメーションを追加します。ここで必要なのは、<li>
をコンポーネントでラップし、props を使用してexit
各コンポーネントが削除された<li>
ときにAnimatePresence
何が起こるかを指定することだけです。それがどのように機能するかを見てみましょう:
<ul>
<AnimatePresence>
{notificationContent.map((content) => {
return (
<motion.li
key={content}
initial={
{
opacity: 0 }}
animate={
{
opacity: 1 }}
exit={
{
x: "-12rem", opacity: 0 }}
transition={
{
duration: 0.2 }}
layout
>
....
</motion.li>
)
})}
</AnimatePresence>
</ul>
exit
プロパティによれば、削除されると<li>
、左に 12rem (トレイの幅の半分) 移動し、アンロードする前に消えるはずです。このlayout
プロパティは、レイアウト オフセットによって引き起こされる要素の位置の変更をアニメーション化するように Framer Motion に指示します。これは、 a がトレイから取り除かれると、その<li>
兄弟が飛び上がってスペースを埋めるのではなく、スムーズに新しい位置に滑り込むことを意味します。時間をかけてご自身で確認してください。
このセクションの最後のタスクは、トレイの出口 (ベルをクリックした後にトレイが消えるとき) をアニメーション化することです。適用するアニメーション<li>
: 左にスワイプしてフェードアウトする場合と同じ終了アニメーション。
コンポーネントを返しHeader.jsx
、次のようにAnimatePresence
ラップします。NotificationTray
<AnimatePresence>
{showNotifications ? (
<NotificationTray
notificationContent={notificationContent}
handleDeleteNotification={handleDeleteNotification}
></NotificationTray>
) : null}
</AnimatePresence>
次に、NotificationTray.jsx
これをexit
最も外側の div に追加します。
<motion.div
className="notification__tray"
initial={
{
opacity: 0 }}
animate={
{
opacity: 1 }}
exit={
{
opacity: 0, x: "-12rem" }}
>
<ul>...</ul>
</motion.div>
これでアニメーションの基礎が完成しました。トレイには次のようなアニメーションが表示されるはずです。