Leave Yamada UI a star

Star
Yamada UIYamada UIv1.4.2

Animation

Yamada UI provides many utilities, such as Motion, a component specialized for animation, and useAnimation, which can be described like CSS's keyframes.

CSS Animation

Yamada UI provides useAnimation and useDynamicAnimation that can be described like CSS's keyframes. Also, by setting tokens in the theme, you can ensure consistency of animation in the project.

Usage of useAnimation

useAnimation sets arguments such as keyframes, and passes the generated animation to props.

  • keyframes: Set the style of keyframes (or intermediate points) along the flow of the animation. The values of each style can use the tokens of the Yamada UI style system and theme.
  • duration: Set the length of time required for one animation cycle.
  • timingFunction: Set how the animation progresses. This defines the acceleration curve, setting how the animation between keyframes progresses.
  • delay: Set the delay time from when the element is loaded until the animation begins.
  • iterationCount: Set the number of times the animation repeats. To repeat the animation indefinitely, specify infinite.
  • direction: Set whether to animate in the reverse direction and repeat when the animation sequence is completed, or to reset to the initial state and repeat the animation.
  • fillMode: Set whether to apply the specified style before and after the execution of the animation.
  • playState: Set whether to pause or resume the animation.

Editable example

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} />
Copied!

Using Multiple Animations

By passing an array of animations to useAnimation, you can use multiple animations.

Editable example

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>
)
Copied!

Responsive Styles

By passing an object to animation in props, it supports PC-first responsive styles.

Editable example

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 }}
  />
)
Copied!

Color Modes

By passing an array to animation in props, it supports color modes.

Editable example

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]} />
Copied!

Usage of useDynamicAnimation

useDynamicAnimation takes an object as an argument. The keys of the object become the keys of the animation, and the animation changes by passing the key as an argument to setState.

Editable example

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>
)
Copied!

Combining with multiple animations

It is also possible to combine dynamic animations with multiple animations.

Editable example

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>
)
Copied!

Using Theme Tokens

Create animation tokens and set them in the theme.

./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",
},
}
Copied!

./theme/tokens/index.ts

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

./theme/index.ts

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

The created tokens are specified in the animation of the component's props. They can also be used with useAnimation and useDynamicAnimation.

Editable example

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

Motion

You can easily implement many animations by using the Motion component.

Animation

By setting styles to initial, animate, and exit, you can implement animations to elements. You can also set the required time and delay time in transition.

  • initial: This is a style object for the initial value of the element.
  • animate: This is a style object that you want to change (animate) from initial.
  • exit: This is a style object that you want to change (animate) when the element is removed from the DOM tree.
  • transition: This is an object to set the required time and delay time.

Editable example

<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>
Copied!

Usage of AnimatePresence

In React, animations are not maintained when elements are removed from the DOM tree. By wrapping with the AnimatePresence component, the element is maintained on the DOM tree until the animation ends.

Editable example

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>
  </>
)
Copied!

Usage of useMotionAnimationFrame

useMotionAnimationFrame executes a callback once for each animation frame of the element. The callback can get the total time since the callback was first called and the total time from the last animation frame.

Editable example

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>
)
Copied!

Keyframes

By passing an array to the property of the style object, you can set keyframes. Each keyframe is placed at an equal interval throughout the animation. You can override the interval by setting times in transition.

Editable example

<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>
Copied!

Gestures

You can detect hover, click, tap, and focus and implement animations.

Hover

The hover gesture detects whether the pointer has moved over or away from an element.

The difference between onMouseEnter and onMouseLeave is that they are guaranteed to fire only as a result of actual mouse events.

  • whileHover: An animation that fires when the element is hovered.
  • onHoverStart: A callback function that fires when the pointer moves over the element.
  • onHoverEnd: A callback function that fires when the pointer moves away from the element.

Editable example

<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>
Copied!

Click/Tap

The click/tap gesture detects whether the primary pointer (such as a left click or touch) has pressed down or released the same element.

  • whileTap: An animation that fires when the element is clicked/tapped.
  • onTap: A callback function that fires when a click/tap on the element has successfully ended.
  • onTapStart: A callback function that fires when a click/tap on the element has started.
  • onTapCancel: A callback function that fires when a click/tap has been cancelled (released outside the element).

Editable example

<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>
Copied!

Focus

The focus gesture detects whether an element has been focused, following the same rules as the CSS selector focus-visible.

Editable example

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

Drag

Detects the movement of the pointer and makes the element follow it.

If you want to enable dragging on an element, set drag to true or pass x or y. If you pass x or y, it will only follow the x-axis or y-axis.

  • onDrag: A callback function that fires during dragging.
  • onDragStart: A callback function that fires when dragging starts.
  • onDragEnd: A callback function that fires when dragging ends.

Editable example

<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>
Copied!

Constraining the Possible Area

You can constrain the possible area of dragging by passing an object with values (in pixels) for top, right, bottom, and left to dragConstraints, or by passing a ref.

Editable example

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>
)
Copied!

Elasticity and Momentum

By passing true, false, a number, or an object with numbers set for top, right, bottom, and left to dragElastic, you can set the degree of allowed movement outside the constraints. 0 means no movement, and 1 means full movement. The default is set to 0.5.

Editable example

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>
)
Copied!

By passing true or false to dragMomentum, you can apply the momentum of the pan gesture to the element. The default is set to true.

Editable example

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>
)
Copied!

Scroll

You can detect when an element enters and leaves the viewport and implement animations.

  • whileInView: An animation that fires when an element enters the viewport.
  • viewport: An object that sets how the viewport is detected.
    • once: If true, the animation fires only once when the element enters the viewport.
    • root: By passing a ref, the viewport of the specified element is used. If no element is specified, the window's viewport is used.
    • margin: The margin added to the viewport when detecting whether an element has entered the viewport. By default, it is "0px". It is described like the margin of CSS (e.g., "0px -20px 0px 100px").
    • amount: By passing "some", "all", or a number, you can set the height of the element that needs to intersect with the viewport. The default is "some".
  • onViewportEnter: A callback function that fires when an element enters the viewport.
  • onViewportLeave: A callback function that fires when an element leaves the viewport.

Editable example

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>
  </>
)
Copied!

Usage of useScroll

useScroll returns four instances. By using these instances, you can implement animations according to the scroll.

  • scrollX: An instance of scroll information (x-axis) between the window's offsets.
  • scrollY: An instance of scroll information (y-axis) between the window's offsets.
  • scrollXProgress: An instance of scroll information (x-axis) between the offsets of the element specified in the argument.
  • scrollYProgress: An instance of scroll information (y-axis) between the offsets of the element specified in the argument.

Editable example

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>
)
Copied!

Global Configuration

If you want to apply a common configuration to the Motion component across your entire project, set it in the config of UIProvider.

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

Edit this page on GitHub

PreviousAt-RulesNextGradient