Motion
Motion
is a convenient component that extends the Yamada UI style system to framer-motion
.
Import
import { Motion } from "@yamada-ui/react"
Usage
The Motion
component internally uses Framer Motion. If you want to know more about the component's features, please check here.
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) frominitial
.exit
: This is a style object that you want to change (animate) when the element is removed from theDOM tree
.transition
: This is an object to set the required time and delay time.
The style objects used in initial
, animate
, exit
, and transition
are not Style props
of Yamada UI. For the properties of the style object, please check Framer Motion.
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>
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> </> )
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> )
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>
If you want to know more about animation, please check here.
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>
If you want to know more about hover animations, please check here.
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>
If you want to know more about click/tap animations, please check here.
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>
If you want to know more about focus animations, please check here.
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>
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> )
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> )
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> )
If you want to know more about drag animation, please check here.
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
: Iftrue
, the animation fires only once when the element enters the viewport.root
: By passing aref
, 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 themargin
ofCSS
(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> </> )
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> )
If you want to know more about scroll animations, please check here.
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>)}
If you want to change the configuration, please check here.
Edit this page on GitHub