Motion
Motionは、MotionにYamada UIのStyle Propsを拡張した便利なコンポーネントです。
<Center h="sm">
<Motion
boxSize="4xs"
bg="bg.contrast"
color="fg.contrast"
animate={{
scale: [1, 2, 2, 1, 1],
rotate: [0, 0, 180, 180, 0],
borderRadius: ["0%", "0%", "50%", "50%", "0%"],
}}
transition={{
duration: 2,
ease: "easeInOut",
times: [0, 0.2, 0.5, 0.8, 1],
repeat: Infinity,
repeatDelay: 1,
}}
/>
</Center>
使い方
import { Motion } from "@yamada-ui/react"
import { Motion } from "@/components/ui"
import { Motion } from "@workspaces/ui"
initial: コンポーネントの初期状態。animate: コンポーネントがマウントされた時や更新された時に実行されるアニメーション。exit: コンポーネントがアンマウントされた時に実行されるアニメーション。transition: 所用時間や遅延時間を設定するオブジェクト。
initial・animate・exitで使われるスタイルオブジェクトは、Yamada UIのStyle Propsではありません。スタイルオブジェクトの各プロパティは、Motionのドキュメントをご覧ください。exitのアニメーションを有効にする場合は、コンポーネントがAnimatePresenceの子要素である必要があります。バリアント
バリアントを設定すると、動的なアニメーションの実装に便利です。他にもアニメーションをオーケストレーションすることも可能です。
Look at me!
const [visible, { toggle }] = useBoolean()
return (
<VStack>
<Button alignSelf="flex-start" onClick={toggle}>
Click me!
</Button>
<Motion
w="fit-content"
p="md"
bg="bg.contrast"
color="fg.contrast"
initial={false}
animate={visible ? "visible" : "hidden"}
transition={{ duration: 1 }}
variants={{
visible: { opacity: 1 },
hidden: { opacity: 0 },
}}
>
Look at me!
</Motion>
</VStack>
)
こちらのコードを使用する場合は、
"use client"をファイルの上部に追加する必要があります。バリアントのアニメーションをもっと知りたい場合は、こちらをご覧ください。
AnimatePresenceを使う
Reactでは、コンポーネントがアンマウントされた時、アニメーションは維持されません。AnimatePresenceを使用することで、アニメーションが終了するまでコンポーネントはアンマウントされません。
const [visible, { toggle }] = useBoolean()
return (
<VStack h="sm">
<Button alignSelf="flex-start" onClick={toggle}>
Click me!
</Button>
<Center flex="1" gap="md">
<AnimatePresence>
{visible ? (
<Motion
w="fit-content"
p="md"
bg="bg.contrast"
color="fg.contrast"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 1 }}
>
Enabled "AnimatePresence"
</Motion>
) : null}
</AnimatePresence>
{visible ? (
<Motion
w="fit-content"
p="md"
bg="bg.contrast"
color="fg.contrast"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 1 }}
>
Disabled "AnimatePresence"
</Motion>
) : null}
</Center>
</VStack>
)
こちらのコードを使用する場合は、
"use client"をファイルの上部に追加する必要があります。キーフレーム
値を配列にすることで、キーフレームを設定できます。各フレームは、均等な間隔で処理されます。transitionのtimesに間隔を設定した配列を設定するとオーバーライドできます。
<Center h="sm">
<Motion
boxSize="4xs"
bg="bg.contrast"
color="fg.contrast"
animate={{
scale: [1, 2, 2, 1, 1],
rotate: [0, 0, 180, 180, 0],
borderRadius: ["0%", "0%", "50%", "50%", "0%"],
}}
transition={{
duration: 2,
ease: "easeInOut",
times: [0, 0.2, 0.5, 0.8, 1],
repeat: Infinity,
repeatDelay: 1,
}}
/>
</Center>
ジェスチャー
ホバー・クリック・タップ・フォーカスを検出し、アニメーションを実行することができます。
ホバー
whileHover: ポインターがコンポーネント上を移動したときに実行されるアニメーション。onHoverStart: ポインターがコンポーネント上を移動したときに実行されるコールバック関数。onHoverEnd: ポインターがコンポーネントから離れたときに実行されるコールバック関数。
Hover me!
<Motion
cursor="pointer"
w="fit-content"
p="md"
bg="bg.contrast"
color="fg.contrast"
whileHover={{ scale: 1.1 }}
onHoverStart={(ev) => console.log("Hover starts")}
onHoverEnd={(ev) => console.log("Hover ends")}
>
Hover me!
</Motion>
こちらのコードを使用する場合は、
"use client"をファイルの上部に追加する必要があります。ホバーのアニメーションをもっと知りたい場合は、こちらをご覧ください。
クリック・タップ
whileTap: ポインターがコンポーネントをクリックまたはタップしたときに実行されるアニメーション。onTapStart: ポインターがコンポーネントを押し始めたときに実行されるコールバック関数。onTap: ポインターがコンポーネントの押し下げをキャンセルし、コンポーネント内でポインターが解放されたときに実行されるコールバック関数。onTapCancel: ポインターがコンポーネントの押し下げをキャンセルし、コンポーネント外でポインターが解放されたときに実行されるコールバック関数。
Click and Tap me!
<Motion
cursor="pointer"
w="fit-content"
p="md"
bg="bg.contrast"
color="fg.contrast"
whileTap={{ scale: 0.9 }}
onTapStart={(ev) => console.log("Tap starts")}
onTap={(ev) => console.log("Tap")}
onTapCancel={(ev) => console.log("Tap cancels")}
>
Click and Tap me!
</Motion>
こちらのコードを使用する場合は、
"use client"をファイルの上部に追加する必要があります。クリック・タップのアニメーションをもっと知りたい場合は、こちらをご覧ください。
フォーカス
whileFocus: コンポーネントがフォーカスされたときに実行されるアニメーション。
Focus me!
<Motion
cursor="pointer"
w="fit-content"
p="md"
bg="bg.contrast"
color="fg.contrast"
tabIndex={0}
whileFocus={{ scale: 1.1 }}
>
Focus me!
</Motion>
こちらのコードを使用する場合は、
"use client"をファイルの上部に追加する必要があります。フォーカスのアニメーションをもっと知りたい場合は、こちらをご覧ください。
ドラッグ
コンポーネントのドラッグを有効にする場合は、dragをtrueに設定します。または"x"または"y"を設定することで、x軸またはy軸のみを追従します。
whileDrag: コンポーネントがドラッグされたときに実行されるアニメーション。onDrag: コンポーネントがドラッグ中に実行されるコールバック関数。onDragStart: コンポーネントがドラッグを開始したときに実行されるコールバック関数。onDragEnd: コンポーネントがドラッグを終了したときに実行されるコールバック関数。
Drag me!
Only X
Only Y
<Center h="sm" gap="md">
<For each={[true, "x", "y"]}>
{(drag) => (
<Motion
cursor={{ base: "grab", _active: "grabbing" }}
w="fit-content"
p="md"
bg="bg.contrast"
color="fg.contrast"
drag={drag}
onDrag={(ev, { point }) =>
console.log("Drag", "x:", point.x, "y:", point.y)
}
onDragStart={(ev, { point }) =>
console.log("Drag starts", "x:", point.x, "y:", point.y)
}
onDragEnd={(ev, { point }) =>
console.log("Drag ends", "x:", point.x, "y:", point.y)
}
>
{drag === true ? "Drag me!" : drag === "x" ? "Only X" : "Only Y"}
</Motion>
)}
</For>
</Center>
こちらのコードを使用する場合は、
"use client"をファイルの上部に追加する必要があります。ドラッグのアニメーションをもっと知りたい場合は、こちらをご覧ください。
可能領域を制限する
可能領域を制限する場合は、dragConstraintsのtop・bottom・left・rightに値(ピクセル)を設定します。
Only Right & Bottom
<Center h="sm" gap="md">
<Motion
cursor={{ base: "grab", _active: "grabbing" }}
w="fit-content"
p="md"
bg="bg.contrast"
color="fg.contrast"
drag
dragConstraints={{ top: 0, right: 100, bottom: 100, left: 0 }}
>
Only Right & Bottom
</Motion>
</Center>
または、refを設定することで可能領域を制限することも可能です。
Drag me!
const ref = useRef<HTMLDivElement>(null)
return (
<Center ref={ref} h="sm" gap="md">
<Motion
cursor={{ base: "grab", _active: "grabbing" }}
w="fit-content"
p="md"
bg="bg.contrast"
color="fg.contrast"
drag
dragConstraints={ref}
>
Drag me!
</Motion>
</Center>
)
こちらのコードを使用する場合は、
"use client"をファイルの上部に追加する必要があります。弾力を設定する
弾力を設定する場合は、dragElasticに真偽値・数値またはtop・bottom・left・rightごとに値(ピクセル)を設定したオブジェクトを設定します。
Drag me!
const ref = useRef<HTMLDivElement>(null)
return (
<Center ref={ref} h="sm" gap="md">
<Motion
cursor={{ base: "grab", _active: "grabbing" }}
w="fit-content"
p="md"
bg="bg.contrast"
color="fg.contrast"
drag
dragElastic={0}
dragConstraints={ref}
>
Drag me!
</Motion>
</Center>
)
こちらのコードを使用する場合は、
"use client"をファイルの上部に追加する必要があります。勢いを設定する
勢いを設定する場合は、dragMomentumに真偽値を設定します。
Drag me!
const ref = useRef<HTMLDivElement>(null)
return (
<Center ref={ref} h="sm" gap="md">
<Motion
cursor={{ base: "grab", _active: "grabbing" }}
w="fit-content"
p="md"
bg="bg.contrast"
color="fg.contrast"
drag
dragMomentum={false}
dragConstraints={ref}
>
Drag me!
</Motion>
</Center>
)
こちらのコードを使用する場合は、
"use client"をファイルの上部に追加する必要があります。方向を制限する
方向を制限する場合は、dragDirectionLockをtrueに設定します。
Drag me!
const [direction, setDirection] = useState<"x" | "y" | null>(null)
return (
<Center position="relative" h="sm" gap="md">
<Box
h="full"
border={`1px dashed {colors.${direction === "y" ? "bg.contrast" : "border"}}`}
position="absolute"
top="50%"
left="50%"
transform="translate(-50%, -50%)"
zIndex="-1"
/>
<Box
w="full"
border={`1px dashed {colors.${direction === "x" ? "bg.contrast" : "border"}}`}
position="absolute"
top="50%"
left="50%"
transform="translate(-50%, -50%)"
zIndex="-1"
/>
<Motion
cursor={{ base: "grab", _active: "grabbing" }}
w="fit-content"
p="md"
border="1px solid {colors.bg.contrast}"
drag
dragDirectionLock
dragConstraints={{ top: 0, right: 0, bottom: 0, left: 0 }}
dragTransition={{ bounceStiffness: 500, bounceDamping: 15 }}
dragElastic={0.2}
onDirectionLock={(direction) => setDirection(direction)}
onDragEnd={() => setDirection(null)}
>
Drag me!
</Motion>
</Center>
)
こちらのコードを使用する場合は、
"use client"をファイルの上部に追加する必要があります。スクロール
whileInView: コンポーネントがビューポートに入ったときに実行されるアニメーション。viewport: ビューポートの検出方法を設定するオブジェクト。once:trueの場合は、コンポーネントがビューポートに入った1回目にアニメーションを実行します。root: スクロール可能な要素(ref)を設定することで、windowの代わりにその要素をビューポートとして使用します。margin: ビューポートに追加するマージン。amount:"some"・"all"・数値を設定することで、ビューポートと交差する必要がある要素の高さを設定します。
onViewportEnter: コンポーネントがビューポートに入ったときに実行されるコールバック関数。onViewportLeave: コンポーネントがビューポートから出たときに実行されるコールバック関数。
Scroll me!
You found me!
Once me!
const ref = useRef<HTMLDivElement>(null)
return (
<VStack ref={ref} maxH="sm" overflowY="auto">
<Text>Scroll me!</Text>
<Spacer />
<Center gap="md" mt="{sizes.xl}">
<For each={[false, true]}>
{(once) => (
<Motion
cursor="pointer"
w="fit-content"
p="md"
bg="bg.contrast"
color="fg.contrast"
initial={{ x: once ? 100 : -100 }}
whileInView={{ x: 0 }}
viewport={{ root: ref, once }}
transition={{ duration: 1 }}
onViewportEnter={(entry) => console.log("Scroll entires", entry)}
onViewportLeave={(entry) => console.log("Scroll leaves", entry)}
>
{once ? "Once me!" : "You found me!"}
</Motion>
)}
</For>
</Center>
</VStack>
)
こちらのコードを使用する場合は、
"use client"をファイルの上部に追加する必要があります。設定
プロジェクト全体でMotionの共通の設定をする場合は、MotionConfigを使用します。
import { MotionConfig } from "motion/react"
import { UIProvider } from "@yamada-ui/react"
const App = () => {
return (
<MotionConfig transition={{ duration: 1 }}>
<UIProvider>
<YourApplication />
</UIProvider>
</MotionConfig>
)
}