Yamada UIにスターをあげる

スター
Yamada UIYamada UIv1.3.10

アニメーション

Yamada UIは、アニメーションに特化したコンポーネントのMotionやCSSのkeyframesのように記述できるuseAnimationなど、多くのユーティリティを提供しています。

CSSアニメーション

Yamada UIは、CSSのkeyframesのように記述できるuseAnimationuseDynamicAnimationを提供しています。また、テーマにトークンを設定することで、プロジェクトにアニメーションの一貫性を担保することができます。

useAnimationを使う

useAnimationは、引数にkeyframesなどを設定し、生成されたanimationpropsに渡します。

  • keyframes: アニメーションの流れに沿ったキーフレーム(または中間地点)のスタイルを設定します。各スタイルの値は、Yamada UIのスタイルシステムやテーマのトークンが使用できます。
  • duration: 1回のアニメーションサイクルに要する時間の長さを設定します。
  • timingFunction: アニメーションの進め方を設定します。これは加速曲線を定義することで、キーフレーム間のアニメーションをどのように進めていくかを設定します。
  • delay: 要素が読み込まれてからアニメーションを始めるまでの遅延時間を設定します。
  • iterationCount: アニメーションを繰り返す回数を設定します。アニメーションを無限に繰り返すにはinfiniteを指定してください。
  • direction: アニメーションのシーケンス完了時に、逆方向にアニメーションして繰り返すか、始めの状態にリセットしてアニメーションを繰り返すかを設定します。
  • fillMode: アニメーションの実行前後に、指定したスタイルを適用するかを設定します。
  • playState: アニメーションを一時停止したり、再開したりするかを設定します。

編集可能な例

const animation = useAnimation({
  keyframes: {
    "0%": {
      bg: "red.500",
    },
    "20%": {
      bg: "green.500",
    },
    "40%": {
      bg: "purple.500",
    },
    "60%": {
      bg: "yellow.500",
    },
    "80%": {
      bg: "blue.500",
    },
    "100%": {
      bg: "red.500",
    },
  },
  duration: "10s",
  iterationCount: "infinite",
  timingFunction: "linear",
})

return <Box w="full" h="xs" animation={animation} />

複数のアニメーションを使う

useAnimationにアニメーションの配列を渡すことで、複数のアニメーションを使うことができます。

編集可能な例

const animation = useAnimation([
  {
    keyframes: {
      "0%": {
        bg: "red.500",
      },
      "20%": {
        bg: "green.500",
      },
      "40%": {
        bg: "purple.500",
      },
      "60%": {
        bg: "yellow.500",
      },
      "80%": {
        bg: "blue.500",
      },
      "100%": {
        bg: "red.500",
      },
    },
    duration: "10s",
    iterationCount: "infinite",
    timingFunction: "linear",
  },
  {
    keyframes: {
      "0%": {
        h: "xs",
      },
      "50%": {
        h: "md",
      },
      "100%": {
        h: "xs",
      },
    },
    duration: "10s",
    iterationCount: "infinite",
    timingFunction: "linear",
  },
  {
    keyframes: {
      "0%": {
        w: "full",
      },
      "50%": {
        w: "50%",
      },
      "100%": {
        w: "full",
      },
    },
    duration: "10s",
    iterationCount: "infinite",
    timingFunction: "linear",
  },
])

return (
  <Box h="md">
    <Box w="full" h="xs" animation={animation} />
  </Box>
)

レスポンシブスタイル

propsanimationにオブジェクトを渡すだけでPCファーストのレスポンシブスタイルに対応します。

編集可能な例

const desktopAnimation = useAnimation({
  keyframes: {
    "0%": {
      bg: "red.500",
    },
    "20%": {
      bg: "green.500",
    },
    "40%": {
      bg: "purple.500",
    },
    "60%": {
      bg: "yellow.500",
    },
    "80%": {
      bg: "blue.500",
    },
    "100%": {
      bg: "red.500",
    },
  },
  duration: "10s",
  iterationCount: "infinite",
  timingFunction: "linear",
})

const tabletAnimation = useAnimation({
  keyframes: {
    "0%": {
      bg: "cyan.500",
    },
    "20%": {
      bg: "emerald.500",
    },
    "40%": {
      bg: "pink.500",
    },
    "60%": {
      bg: "amber.500",
    },
    "80%": {
      bg: "sky.500",
    },
    "100%": {
      bg: "cyan.500",
    },
  },
  duration: "10s",
  iterationCount: "infinite",
  timingFunction: "linear",
})

return (
  <Box
    w="full"
    h="xs"
    animation={{ base: desktopAnimation, md: tabletAnimation }}
  />
)

カラーモード

propsanimationに配列を渡すことでカラーモードに対応します。

編集可能な例

const lightAnimation = useAnimation({
  keyframes: {
    "0%": {
      bg: "red.500",
    },
    "20%": {
      bg: "green.500",
    },
    "40%": {
      bg: "purple.500",
    },
    "60%": {
      bg: "yellow.500",
    },
    "80%": {
      bg: "blue.500",
    },
    "100%": {
      bg: "red.500",
    },
  },
  duration: "10s",
  iterationCount: "infinite",
  timingFunction: "linear",
})

const darkAnimation = useAnimation({
  keyframes: {
    "0%": {
      bg: "red.800",
    },
    "20%": {
      bg: "green.800",
    },
    "40%": {
      bg: "purple.800",
    },
    "60%": {
      bg: "yellow.800",
    },
    "80%": {
      bg: "blue.800",
    },
    "100%": {
      bg: "red.800",
    },
  },
  duration: "10s",
  iterationCount: "infinite",
  timingFunction: "linear",
})

return <Box w="full" h="xs" animation={[lightAnimation, darkAnimation]} />

useDynamicAnimationを使う

useDynamicAnimationは、引数にオブジェクトを渡します。オブジェクトのキーはアニメーションのキーになり、setStateの引数にキーを渡すことでアニメーションが変更されます。

編集可能な例

const [animation, setAnimation] = useDynamicAnimation({
  moveLeft: {
    keyframes: {
      "0%": {
        transform: "translateX(400%)",
      },
      "100%": {
        transform: "translateX(0%)",
      },
    },
    duration: "slower",
    fillMode: "forwards",
    timingFunction: "ease-in-out",
  },
  moveRight: {
    keyframes: {
      "0%": {
        transform: "translateX(0%)",
      },
      "100%": {
        transform: "translateX(400%)",
      },
    },
    duration: "slower",
    fillMode: "forwards",
    timingFunction: "ease-in-out",
  },
})

return (
  <VStack alignItems="flex-start">
    <Button
      onClick={() =>
        setAnimation((prev) =>
          prev === "moveRight" ? "moveLeft" : "moveRight",
        )
      }
    >
      Click me!
    </Button>

    <Box bg="primary" p="md" rounded="md" color="white" animation={animation}>
      Box
    </Box>
  </VStack>
)

複数のアニメーションと組み合わせる

動的なアニメーションと複数のアニメーションを組み合わせることも可能です。

編集可能な例

const [animation, setAnimation] = useDynamicAnimation<
  Record<"moveLeft" | "moveRight", AnimationStyle[]>
>({
  moveLeft: [
    {
      keyframes: {
        "0%": {
          transform: "translateX(400%)",
        },
        "100%": {
          transform: "translateX(0%)",
        },
      },
      duration: "slower",
      fillMode: "forwards",
      timingFunction: "ease-in-out",
    },
    {
      keyframes: {
        "0%": {
          bg: "secondary",
        },
        "100%": {
          bg: "primary",
        },
      },
      duration: "slower",
      fillMode: "forwards",
      timingFunction: "ease-in-out",
    },
  ],
  moveRight: [
    {
      keyframes: {
        "0%": {
          transform: "translateX(0%)",
        },
        "100%": {
          transform: "translateX(400%)",
        },
      },
      duration: "slower",
      fillMode: "forwards",
      timingFunction: "ease-in-out",
    },
    {
      keyframes: {
        "0%": {
          bg: "primary",
        },
        "100%": {
          bg: "secondary",
        },
      },
      duration: "slower",
      fillMode: "forwards",
      timingFunction: "ease-in-out",
    },
  ],
})

return (
  <VStack alignItems="flex-start">
    <Button
      onClick={() =>
        setAnimation((prev) =>
          prev === "moveRight" ? "moveLeft" : "moveRight",
        )
      }
    >
      Click me!
    </Button>

    <Box bg="primary" p="md" rounded="md" color="white" animation={animation}>
      Box
    </Box>
  </VStack>
)

テーマのトークンを使う

アニメーションのトークンを作成し、テーマに設定します。

./theme/tokens/animations.ts

import { ThemeAnimationTokens } from "@yamada-ui/react"
export const animations: ThemeAnimationTokens = {
gradient: {
keyframes: {
"0%": {
bg: "red.500",
},
"20%": {
bg: "green.500",
},
"40%": {
bg: "purple.500",
},
"60%": {
bg: "yellow.500",
},
"80%": {
bg: "blue.500",
},
"100%": {
bg: "red.500",
},
},
duration: "10s",
iterationCount: "infinite",
timingFunction: "linear",
},
}

./theme/tokens/index.ts

import { animations } from "./animations"
export default { animations }

./theme/index.ts

import { extendTheme, UsageTheme } from "@yamada-ui/react"
import tokens from "./tokens"
const customTheme: UsageTheme = {
...tokens,
}
export default extendTheme(customTheme)()

作成したトークンは、コンポーネントのpropsanimationで指定します。useAnimationuseDynamicAnimationでも使用することができます。

編集可能な例

return <Box w="full" h="xs" animation="gradient" />

Motion

Motionコンポーネントの使用することで多くのアニメーションを簡単に実装することができます。

アニメーション

initialanimateexitにスタイルを設定することで、要素にアニメーションを実装できます。また、transitionに所要時間や遅延時間を設定することもできます。

  • initial: 要素の初期値のスタイルオブジェクトです。
  • animate: initialから変化(アニメーション)させたいスタイルオブジェクトです。
  • exit: 要素がDOMツリー上から削除されたときに変化(アニメーション)させたいスタイルオブジェクトです。
  • transition: 所要時間や遅延時間を設定するオブジェクトです。

編集可能な例

<Center p="lg">
  <Motion
    initial={{ x: -100 }}
    animate={{ x: 100 }}
    transition={{
      duration: 2,
      ease: "easeInOut",
      repeat: Infinity,
    }}
    p="md"
    rounded="md"
    bg="primary"
    color="white"
  >
    Hover me!
  </Motion>
</Center>

AnimatePresenceを使う

Reactでは、要素がDOMツリーから削除されるとアニメーションは維持されません。AnimatePresenceコンポーネントでラッピングすることで、アニメーションが終了するまで要素はDOMツリー上で維持されます。

編集可能な例

const [isVisible, { toggle }] = useBoolean()

return (
  <>
    <Button onClick={toggle}>Click me!</Button>

    <Center h="3xs" gap="md">
      <AnimatePresence>
        {isVisible ? (
          <Motion
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            transition={{ duration: 1 }}
            bg="primary"
            color="white"
            p="md"
            rounded="md"
          >
            Enabled "AnimatePresence"
          </Motion>
        ) : null}
      </AnimatePresence>

      {isVisible ? (
        <Motion
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: 0 }}
          transition={{ duration: 1 }}
          bg="secondary"
          color="white"
          p="md"
          rounded="md"
        >
          Disabled "AnimatePresence"
        </Motion>
      ) : null}
    </Center>
  </>
)

useMotionAnimationFrameを使う

useMotionAnimationFrameは、要素のアニメーションフレームごとに1回コールバックを実行します。コールバックは、コールバックが最初に呼び出されてからの合計時間最後のアニメーションフレームからの合計時間を取得することができます。

編集可能な例

const containerRef = useRef<HTMLDivElement>(null)

useMotionAnimationFrame((time, delta) => {
  const rotate = Math.sin(time / 10000) * 200
  const y = (1 + Math.sin(time / 1000)) * -50

  containerRef.current.style.transform = `translateY(${y}px) rotateX(${rotate}deg) rotateY(${rotate}deg)`
})

const sides = useMemo(
  () => [
    { transform: "rotateY(0deg) translateZ(60px)", bg: "red.500" },
    { transform: "rotateY(90deg) translateZ(60px)", bg: "orange.500" },
    { transform: "rotateY(180deg) translateZ(60px)", bg: "pink.500" },
    { transform: "rotateY(-90deg) translateZ(60px)", bg: "purple.500" },
    { transform: "rotateX(90deg) translateZ(60px);", bg: "blue.500" },
    { transform: "rotateX(-90deg) translateZ(60px)", bg: "green.500" },
  ],
  [],
)

return (
  <Center h="md">
    <Box css={{ perspective: "800px" }} w="120px" h="120px">
      <Box
        ref={containerRef}
        position="relative"
        w="120px"
        h="120px"
        transformStyle="preserve-3d"
      >
        {sides.map(({ transform, bg }) => (
          <Box
            key={bg.split(".")[0]}
            position="absolute"
            w="full"
            h="full"
            bg={bg}
            opacity="0.6"
            transform={transform}
          />
        ))}
      </Box>
    </Box>
  </Center>
)

キーフレーム

スタイルオブジェクトのプロパティに配列を渡すことで、キーフレームを設定することができます。各キーフレームは、アニメーション全体で均等な間隔で配置されます。transitiontimesを設定すると間隔をオーバーライドすることができます。

編集可能な例

<Center h="md">
  <Motion
    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,
    }}
    w="3xs"
    h="3xs"
    bg="primary"
  />
</Center>

ジェスチャー

ホバー・クリック・タップ・フォーカスを検出し、アニメーションを実装することができます。

ホバー

ホバーのジェスチャは、ポインタが要素上に移動したか離れたかを検出します。

onMouseEnteronMouseLeaveとの違いは、実際のマウスイベントの結果としてのみ発火することが保証されています。

  • whileHover: 要素がホバーされたときに発火するアニメーションです。
  • onHoverStart: ポインタが要素上に移動したときに発火するコールバック関数です。
  • onHoverEnd: ポインタが要素上から離れたときに発火するコールバック関数です。

編集可能な例

<Center p="lg">
  <Motion
    as="button"
    whileHover={{ scale: 1.1 }}
    onHoverStart={(ev) => console.log("Hover starts")}
    onHoverEnd={(ev) => console.log("Hover ends")}
    p="md"
    rounded="md"
    bg="primary"
    color="white"
  >
    Hover me!
  </Motion>
</Center>

クリック・タップ

クリック・タップのジェスチャは、主ポインタ(左クリックやタッチなど)が同じ要素を押し下げか、放したかを検出します。

  • whileTap: 要素がクリック・タップされたときに発火するアニメーションです。
  • onTap: 要素上でクリック・タップが正常に終了したときに発火するコールバック関数です。
  • onTapStart: 要素上でクリック・タップが開始されたときに発火するコールバック関数です。
  • onTapCancel: クリック・タップがキャンセルされた(要素外で放した)ときに発火するコールバック関数です。

編集可能な例

<Center p="lg">
  <Motion
    as="button"
    whileTap={{ scale: 1.1 }}
    onTap={(ev, { point }) =>
      console.log("Tap ends", "x:", point.x, "y:", point.y)
    }
    onTapStart={(ev, { point }) =>
      console.log("Tap starts", "x:", point.x, "y:", point.y)
    }
    onTapCancel={(ev, { point }) =>
      console.log("Tap cancels", "x:", point.x, "y:", point.y)
    }
    p="md"
    rounded="md"
    bg="primary"
    color="white"
  >
    Click me!
  </Motion>
</Center>

フォーカス

フォーカスのジェスチャは、CSSのセレクターであるfocus-visibleと同じルールに従って、要素がフォーカスをされたかを検出します。

編集可能な例

<Center p="lg">
  <Motion
    as="button"
    whileFocus={{ scale: 1.1 }}
    p="md"
    rounded="md"
    bg="primary"
    color="white"
  >
    Focus me!
  </Motion>
</Center>

ドラッグ

ポインタの移動を検出し、要素を追従させます。

要素がドラッグを有効にする場合は、dragtrueにするか、xまたはyを渡します。xまたはyを渡した場合、x軸またはy軸のみ追従します。

  • onDrag: ドラッグ中に発火するコールバック関数です。
  • onDragStart: ドラッグが開始されたときに発火するコールバック関数です。
  • onDragEnd: ドラッグが終了したときに発火するコールバック関数です。

編集可能な例

<Center h="md" gap="md">
  <Motion
    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)
    }
    p="md"
    cursor="grab"
    _active={{ cursor: "grabbing" }}
    rounded="md"
    bg="primary"
    color="white"
  >
    Drag me!
  </Motion>

  <Motion
    drag="x"
    p="md"
    cursor="grab"
    _active={{ cursor: "grabbing" }}
    rounded="md"
    bg="secondary"
    color="white"
  >
    Only X
  </Motion>

  <Motion
    drag="y"
    p="md"
    cursor="grab"
    _active={{ cursor: "grabbing" }}
    rounded="md"
    bg="warning"
    color="white"
  >
    Only Y
  </Motion>
</Center>

可能領域を制約する

dragConstraintstoprightbottomleftごとに値(ピクセル単位)を設定したオブジェクトか、refを渡すことでドラッグの可能領域を制約することができます。

編集可能な例

const containerRef = useRef<HTMLDivElement>(null)

return (
  <Center ref={containerRef} h="md" gap="md">
    <Motion
      drag
      dragConstraints={containerRef}
      p="md"
      cursor="grab"
      _active={{ cursor: "grabbing" }}
      rounded="md"
      bg="primary"
      color="white"
    >
      Drag me!
    </Motion>

    <Motion
      drag
      dragConstraints={{
        top: 0,
        right: 100,
        bottom: 100,
        left: 0,
      }}
      p="md"
      cursor="grab"
      _active={{ cursor: "grabbing" }}
      rounded="md"
      bg="secondary"
      color="white"
    >
      Only right & bottom
    </Motion>
  </Center>
)

弾力と勢い

dragElastictruefalse数値またはtoprightbottomleftごとに数値を設定したオブジェクトを渡すことで、制約の外側で許可された動きの程度を設定できます。0が動きなし、1が完全に動きます。デフォルトは0.5が設定されています。

編集可能な例

const containerRef = useRef<HTMLDivElement>(null)

return (
  <Center ref={containerRef} h="md" p="md" gap="md">
    <Motion
      drag
      dragConstraints={containerRef}
      dragElastic={0}
      p="md"
      cursor="grab"
      _active={{ cursor: "grabbing" }}
      rounded="md"
      bg="primary"
      color="white"
    >
      Drag me!
    </Motion>
  </Center>
)

dragMomentumtrueまたはfalseを渡すことで、パンのジェスチャーの勢いを要素に適応します。デフォルトは、trueが設定されています。

編集可能な例

const containerRef = useRef<HTMLDivElement>(null)

return (
  <Center ref={containerRef} h="md" p="md" gap="md">
    <Motion
      drag
      dragConstraints={containerRef}
      dragMomentum={false}
      p="md"
      cursor="grab"
      _active={{ cursor: "grabbing" }}
      rounded="md"
      bg="primary"
      color="white"
    >
      Drag me!
    </Motion>
  </Center>
)

スクロール

要素がビューポートに出入りしたことを検出し、アニメーションを実装することができます。

  • whileInView: 要素がビューポートに入ったときに発火するアニメーションです。
  • viewport: ビューポートの検出方法を設定するオブジェクトです。
    • once: trueの場合、要素がビューポートに入った1回だけアニメーションが発火します。
    • root: refを渡すことで、指定された要素のビューポートが仕様されます。指定された要素がない場合は、ウィンドウのビューポートが指定されます。
    • margin: 要素がビューポートに入ったかどうかを検出するときにビューポートに追加するマージンです。デフォルトでは"0px"です。CSSmarginのように記述("0px -20px 0px 100px")します。
    • amount: "some""all"・数値を渡すことでビューポートと交差する必要がある要素の高さを設定します。デフォルトは、"some"です。
  • onViewportEnter: 要素がビューポートに入ったときに発火するコールバック関数です。
  • onViewportLeave: 要素がビューポートに離れたときに発火するコールバック関数です。

編集可能な例

const containerRef = useRef<HTMLDivElement>(null)

return (
  <>
    <Text>Please scroll</Text>

    <Box ref={containerRef} h="md" p="md" overflowY="auto">
      <HStack mt="24rem" justifyContent="center">
        <Motion
          initial={{ x: -100 }}
          whileInView={{ x: 0 }}
          viewport={{ root: containerRef }}
          transition={{ duration: 1 }}
          onViewportEnter={(entry) => console.log("Scroll entires", entry)}
          onViewportLeave={(entry) => console.log("Scroll leaves", entry)}
          p="md"
          rounded="md"
          bg="primary"
          color="white"
        >
          Abled to meet me!
        </Motion>

        <Motion
          initial={{ x: 100 }}
          whileInView={{ x: 0 }}
          viewport={{ once: true, root: containerRef }}
          transition={{ duration: 1 }}
          onViewportEnter={(entry) => console.log("Scroll entires", entry)}
          onViewportLeave={(entry) => console.log("Scroll leaves", entry)}
          p="md"
          rounded="md"
          bg="secondary"
          color="white"
        >
          Once me!
        </Motion>
      </HStack>
    </Box>
  </>
)

useScrollを使う

useScrollは、4つのインスタンスを返します。そのインスタンスを使うことで、スクロールに合わせたアニメーションを実装することができます。

  • scrollX: ウィンドウのオフセット間のスクロール(x軸)情報のインスタンス。
  • scrollY: ウィンドウのオフセット間のスクロール(x軸)情報のインスタンス。
  • scrollXProgress: 引数で指定された要素のオフセット間のスクロール(x軸)情報のインスタンス。
  • scrollYProgress: 引数で指定された要素のオフセット間のスクロール(y軸)情報のインスタンス。

編集可能な例

const containerRef = useRef<HTMLDivElement>(null)
const { scrollYProgress } = useScroll({ container: containerRef })
const scale = useTransform(scrollYProgress, [0, 1], [0.2, 2])

return (
  <Box ref={containerRef} position="relative" h="md" overflowY="auto">
    <Box h="9xl">
      <Box
        w="2xs"
        h="2xs"
        position="sticky"
        top="50%"
        left="50%"
        transform="translate(-50%, -50%)"
      >
        <Motion
          style={{ scale }}
          w="full"
          h="full"
          overflow="hidden"
          bg={["blackAlpha.200", "whiteAlpha.200"]}
          rounded="3xl"
        >
          <Motion
            style={{ scaleY: scrollYProgress }}
            w="inherit"
            h="inherit"
            bg="primary"
            transformOrigin="50% 100%"
          />
        </Motion>
      </Box>
    </Box>
  </Box>
)

グローバルなコンフィグ

プロジェクト全体でMotionコンポーネントに共通の設定を付与したい場合は、UIProviderconfigに設定します。

import { UIProvider, extendConfig } from "@yamada-ui/react"
const customConfig = extendConfig({
motion: {
config: {
transition: { duration: 2 },
},
},
})
const App = () => {
return (
<UIProvider config={customConfig}>
<YourApplication />
</UIProvider>
)
}

GitHubでこのページを編集する

アットルールグラデーション