Leave Yamada UI a star

Star
Yamada UIYamada UIv1.5.3

Motion

Motion is a convenient component that extends the Yamada UI style system to framer-motion.

Source@yamada-ui/motion

Import

import { Motion } from "@yamada-ui/react"
Copied!

Usage

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"
  >
    Look 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 useAnimationFrame

useAnimationFrame 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)

useAnimationFrame((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"
        >
          You found 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

PreviousOtherNextCloseButton