motion
motion
组件驱动了 Vue 的 Motion 库中的大多数动画。
每个 HTML 和 SVG 元素都有一个 motion
组件,例如 motion.div
、motion.circle
等。可以将其视为普通的 Vue 组件,但它被增强为支持 120fps 的动画和手势。
用法
从 motion-v
导入 motion
import { motion } from "motion-v"
现在你可以像使用任何普通的 HTML/SVG 组件一样使用它
<motion.div class="box" />
但你也可以访问强大的动画 API,例如 animate
、layout
、whileInView
属性等等。
<motion.div class="box" // Animate when this value changes: :animate="{ scale: 2 }" // Fade in when the element enters the viewport: :whileInView="{ opacity: 1 }" // Animate the component when its layout changes: layout // Style now supports indepedent transforms: :style="{ x: 100 }" />
查看动画指南以获得关于 Vue 的 Motion 库中动画的完整概述。
性能
motion
组件在 Vue 渲染周期之外对值进行动画处理,以提高性能。
使用motion values(Motion 值)而不是 Vue 状态来更新 style
也可以避免重新渲染。
<script setup> import { useMotionValue } from "motion-v" const x = useMotionValue(0) let timeout; onMounted(() => { // Won't trigger a re-render! timeout = setTimeout(() => x.set(100), 1000) }) onUnMounted(()=>{ clearTimeout(timeout) }) </script> <template> <motion.div :style="{ x }" /> </template>
服务器端渲染
motion
组件与服务器端渲染完全兼容,这意味着组件的初始状态将反映在服务器生成的输出中。
// Server will output `translateX(100px)` <motion.div :initial="false" :animate="{ x: 100 }" />
但某些 SVG 属性(如 transform
)除外,这些属性需要 DOM 测量才能计算。
自定义组件
任何 Vue 组件都可以通过将其作为函数传递给 motion.create()
来增强为 motion
组件。
const MotionComponent = motion.create(Component)
重要提示:请确保不要在模板中调用 motion.create()
!这将使每次渲染都创建一个新组件,从而破坏你的动画。
也可以将字符串传递给 motion.create
,这将创建自定义 DOM 元素。
// Will render <custom-element /> into HTML const MotionComponent = motion.create('custom-element')
默认情况下,所有 motion
属性(如 animate
等)都会从转发到所提供组件的 props
中过滤掉。通过提供 forwardMotionProps
配置,所提供的组件将接收这些属性。
motion.create(Component, { forwardMotionProps: true })
Motion Primitive(Motion 原始组件)
对于那些熟悉 Radix UI Primitives 的人来说,Motion 通过 <Motion />
组件提供类似的原始组件模式。
通过 as
属性更改渲染的元素
<script setup> import { Motion } from "motion-v" </script> <template> <Motion as="button"> Click me </Motion> </template>
将子元素用作渲染的元素
<script setup> import { Motion } from "motion-v" </script> <template> <Motion as-child> <button>Click me</button> </Motion> </template>
属性
motion
组件接受以下属性。
动画
initial
motion
组件的初始视觉状态。
可以设置为动画目标
<motion.section :initial="{ opacity: 0, x: 0 }" />
变体
<motion.li initial="visible" />
<motion.div :initial="['visible', 'active']" />
或者设置为 false
以禁用进入动画,并最初渲染为在 animate
中找到的值。
<motion.div :initial="false" :animate="{ opacity: 0 }" />
animate
进入和更新时要动画到的目标。
可以设置为动画目标
<motion.div :initial="{ boxShadow: '0px 0px #000' }" :animate="{ boxShadow: '10px 10px #000' }" />
或变体
<motion.li animate="visible" />
<motion.div initial="hidden" :animate="['visible', 'active']" />
exit
当组件从树中移除时要动画到的目标。可以设置为动画目标或变体。
注意:由于 Vue Transition 组件的限制,要移除的组件必须是 AnimatePresence
的直接子组件才能启用此动画。
<AnimatePresence> <ul v-if="isVisible" key="list"> <motion.li :exit="{ opacity: 0 }" /> </ul> </AnimatePresence>
transition
当动画属性(animate
、whileHover
等)没有定义 transition
时,此组件要使用的默认过渡。
<motion.div :transition="{ type: 'spring' }" :animate="{ scale: 1.2 }" />
variants
此组件的variants变体。
<script setup> const variants = { active: { backgroundColor: "#f00" }, inactive: { backgroundColor: "#fff", transition: { duration: 2 } } } </script> <template> <motion.div :variants="variants" :animate="isActive ? 'active' : 'inactive'" /> </template>
style
普通的 Vue DOM style
属性,增加了对motion values(Motion 值)和独立变换的支持。
<script setup> const x = useMotionValue(30) </script> <tempalte> <motion.div :style="{ x, rotate: 90, originX: 0.5 }" /> </tempalte>
onUpdate
当 motion
组件上的任何值更新时,每帧触发的回调。它提供一个参数,其中包含最新的值。
<motion.article :animate="{ opacity: 1 }" :@update="latest => console.log(latest.opacity)" />
onAnimationStart
当任何动画(布局动画除外,请参阅 onLayoutAnimationStart
)开始时触发的回调。
它提供一个参数,其中包含已启动动画的目标或变体名称。
<motion.circle :animate="{ r: 10 }" @animationStart="latest => console.log(latest.r)" />
onAnimationComplete
当任何动画(布局动画除外,请参阅 onLayoutAnimationComplete
)完成时触发的回调。
它提供一个参数,其中包含已完成动画的目标或变体名称。
<motion.circle :animate="{ r: 10 }" @animationComplete="latest => console.log(latest.r)" />
Hover(悬停)
whileHover
当悬停手势处于活动状态时要标记到的目标或变体。
<!-- As target --> <motion.button :whileHover="{ scale: 1.2 }" />
<!-- As variants --> <motion.div whileHover="hovered" />
onHoverStart
当指针开始悬停在组件上时触发的回调函数。提供触发的 PointerEvent
。
<motion.div @hoverStart="(event) => console.log(event)" />
onHoverEnd
当指针停止悬停在组件上时触发的回调函数。提供触发的 PointerEvent
。
<motion.div @hoverEnd="(event) => console.log(event)" />
Press(按下)
whilePress
当按下手势处于活动状态时要标记到的目标或变体。
<!-- // As target --> <motion.button :whilePress="{ scale: 0.9 }" />
<!-- // As variants --> <motion.div whilePress="tapped" />
onPressStart
当指针开始按下组件时触发的回调函数。提供触发的 PointerEvent
。
<motion.div @pressStart="(event) => console.log(event)" />
onPress
当指针停止按下组件并且指针在组件内部释放时触发的回调函数。提供触发的 PointerEvent
。
<motion.div @press="(event) => console.log(event)" />
onPressCancel
当指针停止按下组件并且指针在组件外部释放时触发的回调函数。提供触发的 PointerEvent
。
<motion.div @pressCancel="(event) => console.log(event)" />
Focus(焦点)
whileFocus
当焦点手势处于活动状态时要标记到的目标或变体。
<!-- As target --> <motion.button :whileFocus="{ outline: 'dashed #000' }" />
<!-- As variants --> <motion.div whileFocus="focused" />
Pan(平移)
onPan
当在此元素上识别到平移手势时触发的回调函数。
注意:为了使平移手势能够正确地与触摸输入一起工作,元素需要在 x/y 轴或两个轴上禁用触摸滚动,使用 touch-action
CSS 规则。
function onPan(event, info) { console.log(info.point.x, info.point.y) } <motion.div @pan="onPan" />
平移和拖动手势事件提供原始 PointerEvent
以及一个 info
对象,该对象包含以下内容的 x
和 y
点值
point
:相对于设备或页面。delta
:自上次事件以来的距离。offset
:与原始事件的距离。velocity
:指针的当前速度。
onPanStart
当平移手势开始时触发的回调函数。提供触发的 PointerEvent
和 info
。
<motion.div @panStart="(event, info) => console.log(info.delta.x)" />
onPanEnd
当平移手势结束时触发的回调函数。提供触发的 PointerEvent
和 info
。
<motion.div @panEnd="(event, info) => console.log(info.delta.x)" />
Drag(拖动)
drag
默认值:false
为此元素启用拖动。设置为 true
以在两个方向上拖动。设置为 "x"
或 "y"
以仅在特定方向上拖动。
<motion.div drag />
whileDrag
当拖动手势处于活动状态时要标记到的目标或变体。
<!-- // As target --> <motion.div drag :whileDrag="{ scale: 0.9 }" />
<!-- // As variants --> <motion.div drag whileDrag="dragging" />
dragConstraints
对可拖动区域应用约束。
设置为可选 top
、left
、right
和 bottom
值的对象,以像素为单位测量
<motion.div drag="x" :dragConstraints="{ left: 0, right: 300 }" />
或作为对另一个元素的 ref
,以使用其边界框作为可拖动约束
<script setup> import { useDomRef } from "motion-v" const constraintsRef = useDomRef() </script> <template> <motion.div ref="constraintsRef"> <motion.div drag :dragConstraints="constraintsRef" /> </motion.div> </template>
dragSnapToOrigin
默认值:false
如果为 true
,则可拖动元素在释放时将动画返回到其中心/原点。
<motion.div drag dragSnapToOrigin />
dragElastic
默认值:0.5
约束外部允许的移动程度。0
= 不移动,1
= 完全移动。
默认设置为 0.5
。也可以设置为 false
以禁用移动。
通过传递 top
/right
/bottom
/left
的对象,可以为每个约束设置单独的值。任何缺失的值都将设置为 0
。
<motion.div drag :dragConstraints="{ left: 0, right: 300 }" :dragElastic="0.2" />
dragMomentum
默认值:true
在拖动完成时,将来自平移手势的动量应用于组件。默认设置为 true
。
<motion.div drag :dragConstraints="{ left: 0, right: 300 }" :dragMomentum="false" />
dragTransition
允许你更改拖动动量过渡。当释放可拖动元素时,会启动类型为 "inertia"
的动画。动画基于你的拖动速度。此属性允许你自定义它。
<motion.div drag :dragTransition="{ bounceStiffness: 600, bounceDamping: 10 }" />
dragDirectionLock
默认值:false
将拖动方向锁定到最早检测到的方向。例如,如果在拖动手势开始之前,组件在 x
轴上的移动多于 y
轴,则在手势的剩余时间内,它将仅在 x
轴上拖动。
<motion.div drag dragDirectionLock />
dragPropagation
默认值:false
允许拖动手势传播到子组件。
<motion.div drag="x" dragPropagation />
dragControls
通常,拖动是通过按下组件并移动它来启动的。对于某些用例,例如单击视频 scrub bar 上的任意点,我们可能希望从与可拖动组件不同的组件启动拖动。
通过使用 useDragControls
创建 dragControls
钩子,我们可以将其传递到可拖动组件的 dragControls
属性中。它公开了一个 start
方法,该方法可以从其他组件上的指针事件开始拖动。
<script setup> const dragControls = useDragControls() function startDrag(event) { dragControls.start(event, { snapToCursor: true }) } </script> <template> <div @pointerDown="startDrag" /> <motion.div drag="x" :dragControls="dragControls" /> </template>
注意: 鉴于通过设置 dragControls
你正在控制启动拖动手势,因此可以通过设置 :dragListener="false"
来禁用可拖动元素作为启动器。
dragListener
确定是否从事件侦听器触发拖动手势。如果传递 dragControls
,则将其设置为 false
将确保拖动只能由控件启动,而不是由可拖动元素上的 pointerdown
事件启动。
onDrag
当在此元素上识别到拖动手势时触发的回调函数。
function onDrag(event, info) { console.log(info.point.x, info.point.y) } <motion.div drag @drag="onDrag" />
平移和拖动手势事件提供原始 PointerEvent
以及一个 info
对象,该对象包含以下内容的 x
和 y
点值
point
:相对于设备或页面。delta
:自上次事件以来的距离。offset
:与原始事件的距离。velocity
:指针的当前速度。
onDragStart
当拖动手势开始时触发的回调函数。提供触发的 PointerEvent
和 info
。
<motion.div drag @dragStart="(event, info) => console.log(info.delta.x)" />
onDragEnd
当拖动手势结束时触发的回调函数。提供触发的 PointerEvent
和 info
。
<motion.div drag @dragEnd="(event, info) => console.log(info.delta.x)" />
onDirectionLock
当拖动方向确定时触发的回调函数。
<motion.div drag dragDirectionLock @directionLock="axis => console.log(axis)" />
Viewport(视口)
whileInView
当元素在视口中时要标记到的目标或变体。
<!-- As target --> <motion.div :whileInView="{ opacity: 1 }" />
// As variants <motion.div whileInView="visible" />
inViewOptions
用于定义如何在视口中跟踪元素的可选配置。
<motion.section :whileInView="{ opacity: 1 }" :inViewOptions="{ once: true }" />
可用选项
once
:如果为true
,则元素一旦进入视口,将不再检测后续的离开/进入事件。root
:祖先可滚动元素的ref
,用于检测与其的交叉(而不是window
)。margin
:添加到视口的边距,以更改检测区域。默认为"0px"
。使用多个值来调整上/右/下/左,例如"0px -20px 0px 100px"
。amount
:元素应进入视口才能被视为“已进入”的数量。可以是"some"
、"all"
或介于0
和1
之间的数字。默认为"some"
。
onViewportEnter
当元素进入视口时触发的回调函数。提供 IntersectionObserverEntry
,其中包含交叉事件的详细信息。
<motion.div @viewportEnter="(entry) => console.log(entry.isIntersecting)" />
onViewportLeave
当元素进入视口时触发的回调函数。提供 IntersectionObserverEntry
,其中包含交叉事件的详细信息。
<motion.div @viewportLeave="(entry) => console.log(entry.intersectionRect)" />
布局
layout
默认值:false
如果为 true
,则此组件将动画化其布局的更改.
<motion.div layout />
如果设置为 "position"
或 "size"
,则分别仅动画化其位置或大小。
<motion.img layout="position" />
layoutId
如果设置,此组件将动画化其布局的更改。此外,当新元素进入 DOM 并且已存在具有匹配 layoutId
的元素时,它将从先前元素的大小/位置动画移出。
<motion.li v-for="item in items" layout> {{item.name}} <motion.div v-if="item.isSelected" layoutId="underline" /> </motion.li>
如果先前的组件保留在树中,则这两个元素将交叉淡入淡出。
layoutDependency
默认情况下,每次渲染都会检测布局更改。为了减少测量次数并提高性能,你可以传递 layoutDependency
属性。仅当此值更改时才会进行测量。
<motion.nav layout :layoutDependency="isOpen" />
layoutScroll
为了使布局动画在可滚动元素中正确工作,需要测量其滚动偏移量。出于性能原因,Motion
不会测量每个祖先的滚动偏移量。将 layoutScroll
属性添加到应测量的元素。
<motion.div layoutScroll style="{ overflow: 'scroll' }"> <motion.div layout /> </motion.div>
layoutRoot
为了使布局动画在 position: fixed
元素中正确工作,我们需要考虑页面滚动。添加 layoutRoot
以将元素标记为 position: fixed
。
<motion.div layoutRoot :style="{ position: 'fixed' }"> <motion.div layout /> </motion.div>
onLayoutAnimationStart
布局动画开始时要运行的回调。
onLayoutAnimationComplete
布局动画完成时要运行的回调。
Advanced(高级)
inherit
设置为 false
以防止组件继承或传播父变体中的更改。
custom
要传递给动态变体的自定义数据。
<script setup> const variants = { visible: (custom) => ({ opacity: 1, transition: { delay: custom * 0.2 } }) } </script> <template> <motion.ul animate="visible"> <motion.li :custom="0" :variants="variants" /> <motion.li :custom="1" :variants="variants" /> <motion.li :custom="2" :variants="variants" /> </motion.ul> </template>
transformTemplate
默认情况下,变换按 translate
、scale
、rotate
和 skew
的顺序应用。
要更改此顺序,可以将 transformTemplate
设置为一个函数,该函数接受最新的变换和生成的变换字符串,并返回一个新的变换字符串。
<!-- // Use the latest transform values --> <motion.div :style="{ x: 0, rotate: 180 }" :transformTemplate="({ x, rotate }) => `rotate(${rotate}deg) translateX(${x}px)`" />
<!-- // Or the generated transform string --> <motion.div :style="{ x: 0, rotate: 180 }" :transformTemplate="(latest, generated) => `translate(-50%, -50%) ${generated}`" />
motion
组件驱动了 Vue 的 Motion 库中的大多数动画。
每个 HTML 和 SVG 元素都有一个 motion
组件,例如 motion.div
、motion.circle
等。可以将其视为普通的 Vue 组件,但它被增强为支持 120fps 的动画和手势。
用法
从 motion-v
导入 motion
import { motion } from "motion-v"
现在你可以像使用任何普通的 HTML/SVG 组件一样使用它
<motion.div class="box" />
但你也可以访问强大的动画 API,例如 animate
、layout
、whileInView
属性等等。
<motion.div class="box" // Animate when this value changes: :animate="{ scale: 2 }" // Fade in when the element enters the viewport: :whileInView="{ opacity: 1 }" // Animate the component when its layout changes: layout // Style now supports indepedent transforms: :style="{ x: 100 }" />
查看动画指南以获得关于 Vue 的 Motion 库中动画的完整概述。
性能
motion
组件在 Vue 渲染周期之外对值进行动画处理,以提高性能。
使用motion values(Motion 值)而不是 Vue 状态来更新 style
也可以避免重新渲染。
<script setup> import { useMotionValue } from "motion-v" const x = useMotionValue(0) let timeout; onMounted(() => { // Won't trigger a re-render! timeout = setTimeout(() => x.set(100), 1000) }) onUnMounted(()=>{ clearTimeout(timeout) }) </script> <template> <motion.div :style="{ x }" /> </template>
服务器端渲染
motion
组件与服务器端渲染完全兼容,这意味着组件的初始状态将反映在服务器生成的输出中。
// Server will output `translateX(100px)` <motion.div :initial="false" :animate="{ x: 100 }" />
但某些 SVG 属性(如 transform
)除外,这些属性需要 DOM 测量才能计算。
自定义组件
任何 Vue 组件都可以通过将其作为函数传递给 motion.create()
来增强为 motion
组件。
const MotionComponent = motion.create(Component)
重要提示:请确保不要在模板中调用 motion.create()
!这将使每次渲染都创建一个新组件,从而破坏你的动画。
也可以将字符串传递给 motion.create
,这将创建自定义 DOM 元素。
// Will render <custom-element /> into HTML const MotionComponent = motion.create('custom-element')
默认情况下,所有 motion
属性(如 animate
等)都会从转发到所提供组件的 props
中过滤掉。通过提供 forwardMotionProps
配置,所提供的组件将接收这些属性。
motion.create(Component, { forwardMotionProps: true })
Motion Primitive(Motion 原始组件)
对于那些熟悉 Radix UI Primitives 的人来说,Motion 通过 <Motion />
组件提供类似的原始组件模式。
通过 as
属性更改渲染的元素
<script setup> import { Motion } from "motion-v" </script> <template> <Motion as="button"> Click me </Motion> </template>
将子元素用作渲染的元素
<script setup> import { Motion } from "motion-v" </script> <template> <Motion as-child> <button>Click me</button> </Motion> </template>
属性
motion
组件接受以下属性。
动画
initial
motion
组件的初始视觉状态。
可以设置为动画目标
<motion.section :initial="{ opacity: 0, x: 0 }" />
变体
<motion.li initial="visible" />
<motion.div :initial="['visible', 'active']" />
或者设置为 false
以禁用进入动画,并最初渲染为在 animate
中找到的值。
<motion.div :initial="false" :animate="{ opacity: 0 }" />
animate
进入和更新时要动画到的目标。
可以设置为动画目标
<motion.div :initial="{ boxShadow: '0px 0px #000' }" :animate="{ boxShadow: '10px 10px #000' }" />
或变体
<motion.li animate="visible" />
<motion.div initial="hidden" :animate="['visible', 'active']" />
exit
当组件从树中移除时要动画到的目标。可以设置为动画目标或变体。
注意:由于 Vue Transition 组件的限制,要移除的组件必须是 AnimatePresence
的直接子组件才能启用此动画。
<AnimatePresence> <ul v-if="isVisible" key="list"> <motion.li :exit="{ opacity: 0 }" /> </ul> </AnimatePresence>
transition
当动画属性(animate
、whileHover
等)没有定义 transition
时,此组件要使用的默认过渡。
<motion.div :transition="{ type: 'spring' }" :animate="{ scale: 1.2 }" />
variants
此组件的variants变体。
<script setup> const variants = { active: { backgroundColor: "#f00" }, inactive: { backgroundColor: "#fff", transition: { duration: 2 } } } </script> <template> <motion.div :variants="variants" :animate="isActive ? 'active' : 'inactive'" /> </template>
style
普通的 Vue DOM style
属性,增加了对motion values(Motion 值)和独立变换的支持。
<script setup> const x = useMotionValue(30) </script> <tempalte> <motion.div :style="{ x, rotate: 90, originX: 0.5 }" /> </tempalte>
onUpdate
当 motion
组件上的任何值更新时,每帧触发的回调。它提供一个参数,其中包含最新的值。
<motion.article :animate="{ opacity: 1 }" :@update="latest => console.log(latest.opacity)" />
onAnimationStart
当任何动画(布局动画除外,请参阅 onLayoutAnimationStart
)开始时触发的回调。
它提供一个参数,其中包含已启动动画的目标或变体名称。
<motion.circle :animate="{ r: 10 }" @animationStart="latest => console.log(latest.r)" />
onAnimationComplete
当任何动画(布局动画除外,请参阅 onLayoutAnimationComplete
)完成时触发的回调。
它提供一个参数,其中包含已完成动画的目标或变体名称。
<motion.circle :animate="{ r: 10 }" @animationComplete="latest => console.log(latest.r)" />
Hover(悬停)
whileHover
当悬停手势处于活动状态时要标记到的目标或变体。
<!-- As target --> <motion.button :whileHover="{ scale: 1.2 }" />
<!-- As variants --> <motion.div whileHover="hovered" />
onHoverStart
当指针开始悬停在组件上时触发的回调函数。提供触发的 PointerEvent
。
<motion.div @hoverStart="(event) => console.log(event)" />
onHoverEnd
当指针停止悬停在组件上时触发的回调函数。提供触发的 PointerEvent
。
<motion.div @hoverEnd="(event) => console.log(event)" />
Press(按下)
whilePress
当按下手势处于活动状态时要标记到的目标或变体。
<!-- // As target --> <motion.button :whilePress="{ scale: 0.9 }" />
<!-- // As variants --> <motion.div whilePress="tapped" />
onPressStart
当指针开始按下组件时触发的回调函数。提供触发的 PointerEvent
。
<motion.div @pressStart="(event) => console.log(event)" />
onPress
当指针停止按下组件并且指针在组件内部释放时触发的回调函数。提供触发的 PointerEvent
。
<motion.div @press="(event) => console.log(event)" />
onPressCancel
当指针停止按下组件并且指针在组件外部释放时触发的回调函数。提供触发的 PointerEvent
。
<motion.div @pressCancel="(event) => console.log(event)" />
Focus(焦点)
whileFocus
当焦点手势处于活动状态时要标记到的目标或变体。
<!-- As target --> <motion.button :whileFocus="{ outline: 'dashed #000' }" />
<!-- As variants --> <motion.div whileFocus="focused" />
Pan(平移)
onPan
当在此元素上识别到平移手势时触发的回调函数。
注意:为了使平移手势能够正确地与触摸输入一起工作,元素需要在 x/y 轴或两个轴上禁用触摸滚动,使用 touch-action
CSS 规则。
function onPan(event, info) { console.log(info.point.x, info.point.y) } <motion.div @pan="onPan" />
平移和拖动手势事件提供原始 PointerEvent
以及一个 info
对象,该对象包含以下内容的 x
和 y
点值
point
:相对于设备或页面。delta
:自上次事件以来的距离。offset
:与原始事件的距离。velocity
:指针的当前速度。
onPanStart
当平移手势开始时触发的回调函数。提供触发的 PointerEvent
和 info
。
<motion.div @panStart="(event, info) => console.log(info.delta.x)" />
onPanEnd
当平移手势结束时触发的回调函数。提供触发的 PointerEvent
和 info
。
<motion.div @panEnd="(event, info) => console.log(info.delta.x)" />
Drag(拖动)
drag
默认值:false
为此元素启用拖动。设置为 true
以在两个方向上拖动。设置为 "x"
或 "y"
以仅在特定方向上拖动。
<motion.div drag />
whileDrag
当拖动手势处于活动状态时要标记到的目标或变体。
<!-- // As target --> <motion.div drag :whileDrag="{ scale: 0.9 }" />
<!-- // As variants --> <motion.div drag whileDrag="dragging" />
dragConstraints
对可拖动区域应用约束。
设置为可选 top
、left
、right
和 bottom
值的对象,以像素为单位测量
<motion.div drag="x" :dragConstraints="{ left: 0, right: 300 }" />
或作为对另一个元素的 ref
,以使用其边界框作为可拖动约束
<script setup> import { useDomRef } from "motion-v" const constraintsRef = useDomRef() </script> <template> <motion.div ref="constraintsRef"> <motion.div drag :dragConstraints="constraintsRef" /> </motion.div> </template>
dragSnapToOrigin
默认值:false
如果为 true
,则可拖动元素在释放时将动画返回到其中心/原点。
<motion.div drag dragSnapToOrigin />
dragElastic
默认值:0.5
约束外部允许的移动程度。0
= 不移动,1
= 完全移动。
默认设置为 0.5
。也可以设置为 false
以禁用移动。
通过传递 top
/right
/bottom
/left
的对象,可以为每个约束设置单独的值。任何缺失的值都将设置为 0
。
<motion.div drag :dragConstraints="{ left: 0, right: 300 }" :dragElastic="0.2" />
dragMomentum
默认值:true
在拖动完成时,将来自平移手势的动量应用于组件。默认设置为 true
。
<motion.div drag :dragConstraints="{ left: 0, right: 300 }" :dragMomentum="false" />
dragTransition
允许你更改拖动动量过渡。当释放可拖动元素时,会启动类型为 "inertia"
的动画。动画基于你的拖动速度。此属性允许你自定义它。
<motion.div drag :dragTransition="{ bounceStiffness: 600, bounceDamping: 10 }" />
dragDirectionLock
默认值:false
将拖动方向锁定到最早检测到的方向。例如,如果在拖动手势开始之前,组件在 x
轴上的移动多于 y
轴,则在手势的剩余时间内,它将仅在 x
轴上拖动。
<motion.div drag dragDirectionLock />
dragPropagation
默认值:false
允许拖动手势传播到子组件。
<motion.div drag="x" dragPropagation />
dragControls
通常,拖动是通过按下组件并移动它来启动的。对于某些用例,例如单击视频 scrub bar 上的任意点,我们可能希望从与可拖动组件不同的组件启动拖动。
通过使用 useDragControls
创建 dragControls
钩子,我们可以将其传递到可拖动组件的 dragControls
属性中。它公开了一个 start
方法,该方法可以从其他组件上的指针事件开始拖动。
<script setup> const dragControls = useDragControls() function startDrag(event) { dragControls.start(event, { snapToCursor: true }) } </script> <template> <div @pointerDown="startDrag" /> <motion.div drag="x" :dragControls="dragControls" /> </template>
注意: 鉴于通过设置 dragControls
你正在控制启动拖动手势,因此可以通过设置 :dragListener="false"
来禁用可拖动元素作为启动器。
dragListener
确定是否从事件侦听器触发拖动手势。如果传递 dragControls
,则将其设置为 false
将确保拖动只能由控件启动,而不是由可拖动元素上的 pointerdown
事件启动。
onDrag
当在此元素上识别到拖动手势时触发的回调函数。
function onDrag(event, info) { console.log(info.point.x, info.point.y) } <motion.div drag @drag="onDrag" />
平移和拖动手势事件提供原始 PointerEvent
以及一个 info
对象,该对象包含以下内容的 x
和 y
点值
point
:相对于设备或页面。delta
:自上次事件以来的距离。offset
:与原始事件的距离。velocity
:指针的当前速度。
onDragStart
当拖动手势开始时触发的回调函数。提供触发的 PointerEvent
和 info
。
<motion.div drag @dragStart="(event, info) => console.log(info.delta.x)" />
onDragEnd
当拖动手势结束时触发的回调函数。提供触发的 PointerEvent
和 info
。
<motion.div drag @dragEnd="(event, info) => console.log(info.delta.x)" />
onDirectionLock
当拖动方向确定时触发的回调函数。
<motion.div drag dragDirectionLock @directionLock="axis => console.log(axis)" />
Viewport(视口)
whileInView
当元素在视口中时要标记到的目标或变体。
<!-- As target --> <motion.div :whileInView="{ opacity: 1 }" />
// As variants <motion.div whileInView="visible" />
inViewOptions
用于定义如何在视口中跟踪元素的可选配置。
<motion.section :whileInView="{ opacity: 1 }" :inViewOptions="{ once: true }" />
可用选项
once
:如果为true
,则元素一旦进入视口,将不再检测后续的离开/进入事件。root
:祖先可滚动元素的ref
,用于检测与其的交叉(而不是window
)。margin
:添加到视口的边距,以更改检测区域。默认为"0px"
。使用多个值来调整上/右/下/左,例如"0px -20px 0px 100px"
。amount
:元素应进入视口才能被视为“已进入”的数量。可以是"some"
、"all"
或介于0
和1
之间的数字。默认为"some"
。
onViewportEnter
当元素进入视口时触发的回调函数。提供 IntersectionObserverEntry
,其中包含交叉事件的详细信息。
<motion.div @viewportEnter="(entry) => console.log(entry.isIntersecting)" />
onViewportLeave
当元素进入视口时触发的回调函数。提供 IntersectionObserverEntry
,其中包含交叉事件的详细信息。
<motion.div @viewportLeave="(entry) => console.log(entry.intersectionRect)" />
布局
layout
默认值:false
如果为 true
,则此组件将动画化其布局的更改.
<motion.div layout />
如果设置为 "position"
或 "size"
,则分别仅动画化其位置或大小。
<motion.img layout="position" />
layoutId
如果设置,此组件将动画化其布局的更改。此外,当新元素进入 DOM 并且已存在具有匹配 layoutId
的元素时,它将从先前元素的大小/位置动画移出。
<motion.li v-for="item in items" layout> {{item.name}} <motion.div v-if="item.isSelected" layoutId="underline" /> </motion.li>
如果先前的组件保留在树中,则这两个元素将交叉淡入淡出。
layoutDependency
默认情况下,每次渲染都会检测布局更改。为了减少测量次数并提高性能,你可以传递 layoutDependency
属性。仅当此值更改时才会进行测量。
<motion.nav layout :layoutDependency="isOpen" />
layoutScroll
为了使布局动画在可滚动元素中正确工作,需要测量其滚动偏移量。出于性能原因,Motion
不会测量每个祖先的滚动偏移量。将 layoutScroll
属性添加到应测量的元素。
<motion.div layoutScroll style="{ overflow: 'scroll' }"> <motion.div layout /> </motion.div>
layoutRoot
为了使布局动画在 position: fixed
元素中正确工作,我们需要考虑页面滚动。添加 layoutRoot
以将元素标记为 position: fixed
。
<motion.div layoutRoot :style="{ position: 'fixed' }"> <motion.div layout /> </motion.div>
onLayoutAnimationStart
布局动画开始时要运行的回调。
onLayoutAnimationComplete
布局动画完成时要运行的回调。
Advanced(高级)
inherit
设置为 false
以防止组件继承或传播父变体中的更改。
custom
要传递给动态变体的自定义数据。
<script setup> const variants = { visible: (custom) => ({ opacity: 1, transition: { delay: custom * 0.2 } }) } </script> <template> <motion.ul animate="visible"> <motion.li :custom="0" :variants="variants" /> <motion.li :custom="1" :variants="variants" /> <motion.li :custom="2" :variants="variants" /> </motion.ul> </template>
transformTemplate
默认情况下,变换按 translate
、scale
、rotate
和 skew
的顺序应用。
要更改此顺序,可以将 transformTemplate
设置为一个函数,该函数接受最新的变换和生成的变换字符串,并返回一个新的变换字符串。
<!-- // Use the latest transform values --> <motion.div :style="{ x: 0, rotate: 180 }" :transformTemplate="({ x, rotate }) => `rotate(${rotate}deg) translateX(${x}px)`" />
<!-- // Or the generated transform string --> <motion.div :style="{ x: 0, rotate: 180 }" :transformTemplate="(latest, generated) => `translate(-50%, -50%) ${generated}`" />
motion
组件驱动了 Vue 的 Motion 库中的大多数动画。
每个 HTML 和 SVG 元素都有一个 motion
组件,例如 motion.div
、motion.circle
等。可以将其视为普通的 Vue 组件,但它被增强为支持 120fps 的动画和手势。
用法
从 motion-v
导入 motion
import { motion } from "motion-v"
现在你可以像使用任何普通的 HTML/SVG 组件一样使用它
<motion.div class="box" />
但你也可以访问强大的动画 API,例如 animate
、layout
、whileInView
属性等等。
<motion.div class="box" // Animate when this value changes: :animate="{ scale: 2 }" // Fade in when the element enters the viewport: :whileInView="{ opacity: 1 }" // Animate the component when its layout changes: layout // Style now supports indepedent transforms: :style="{ x: 100 }" />
查看动画指南以获得关于 Vue 的 Motion 库中动画的完整概述。
性能
motion
组件在 Vue 渲染周期之外对值进行动画处理,以提高性能。
使用motion values(Motion 值)而不是 Vue 状态来更新 style
也可以避免重新渲染。
<script setup> import { useMotionValue } from "motion-v" const x = useMotionValue(0) let timeout; onMounted(() => { // Won't trigger a re-render! timeout = setTimeout(() => x.set(100), 1000) }) onUnMounted(()=>{ clearTimeout(timeout) }) </script> <template> <motion.div :style="{ x }" /> </template>
服务器端渲染
motion
组件与服务器端渲染完全兼容,这意味着组件的初始状态将反映在服务器生成的输出中。
// Server will output `translateX(100px)` <motion.div :initial="false" :animate="{ x: 100 }" />
但某些 SVG 属性(如 transform
)除外,这些属性需要 DOM 测量才能计算。
自定义组件
任何 Vue 组件都可以通过将其作为函数传递给 motion.create()
来增强为 motion
组件。
const MotionComponent = motion.create(Component)
重要提示:请确保不要在模板中调用 motion.create()
!这将使每次渲染都创建一个新组件,从而破坏你的动画。
也可以将字符串传递给 motion.create
,这将创建自定义 DOM 元素。
// Will render <custom-element /> into HTML const MotionComponent = motion.create('custom-element')
默认情况下,所有 motion
属性(如 animate
等)都会从转发到所提供组件的 props
中过滤掉。通过提供 forwardMotionProps
配置,所提供的组件将接收这些属性。
motion.create(Component, { forwardMotionProps: true })
Motion Primitive(Motion 原始组件)
对于那些熟悉 Radix UI Primitives 的人来说,Motion 通过 <Motion />
组件提供类似的原始组件模式。
通过 as
属性更改渲染的元素
<script setup> import { Motion } from "motion-v" </script> <template> <Motion as="button"> Click me </Motion> </template>
将子元素用作渲染的元素
<script setup> import { Motion } from "motion-v" </script> <template> <Motion as-child> <button>Click me</button> </Motion> </template>
属性
motion
组件接受以下属性。
动画
initial
motion
组件的初始视觉状态。
可以设置为动画目标
<motion.section :initial="{ opacity: 0, x: 0 }" />
变体
<motion.li initial="visible" />
<motion.div :initial="['visible', 'active']" />
或者设置为 false
以禁用进入动画,并最初渲染为在 animate
中找到的值。
<motion.div :initial="false" :animate="{ opacity: 0 }" />
animate
进入和更新时要动画到的目标。
可以设置为动画目标
<motion.div :initial="{ boxShadow: '0px 0px #000' }" :animate="{ boxShadow: '10px 10px #000' }" />
或变体
<motion.li animate="visible" />
<motion.div initial="hidden" :animate="['visible', 'active']" />
exit
当组件从树中移除时要动画到的目标。可以设置为动画目标或变体。
注意:由于 Vue Transition 组件的限制,要移除的组件必须是 AnimatePresence
的直接子组件才能启用此动画。
<AnimatePresence> <ul v-if="isVisible" key="list"> <motion.li :exit="{ opacity: 0 }" /> </ul> </AnimatePresence>
transition
当动画属性(animate
、whileHover
等)没有定义 transition
时,此组件要使用的默认过渡。
<motion.div :transition="{ type: 'spring' }" :animate="{ scale: 1.2 }" />
variants
此组件的variants变体。
<script setup> const variants = { active: { backgroundColor: "#f00" }, inactive: { backgroundColor: "#fff", transition: { duration: 2 } } } </script> <template> <motion.div :variants="variants" :animate="isActive ? 'active' : 'inactive'" /> </template>
style
普通的 Vue DOM style
属性,增加了对motion values(Motion 值)和独立变换的支持。
<script setup> const x = useMotionValue(30) </script> <tempalte> <motion.div :style="{ x, rotate: 90, originX: 0.5 }" /> </tempalte>
onUpdate
当 motion
组件上的任何值更新时,每帧触发的回调。它提供一个参数,其中包含最新的值。
<motion.article :animate="{ opacity: 1 }" :@update="latest => console.log(latest.opacity)" />
onAnimationStart
当任何动画(布局动画除外,请参阅 onLayoutAnimationStart
)开始时触发的回调。
它提供一个参数,其中包含已启动动画的目标或变体名称。
<motion.circle :animate="{ r: 10 }" @animationStart="latest => console.log(latest.r)" />
onAnimationComplete
当任何动画(布局动画除外,请参阅 onLayoutAnimationComplete
)完成时触发的回调。
它提供一个参数,其中包含已完成动画的目标或变体名称。
<motion.circle :animate="{ r: 10 }" @animationComplete="latest => console.log(latest.r)" />
Hover(悬停)
whileHover
当悬停手势处于活动状态时要标记到的目标或变体。
<!-- As target --> <motion.button :whileHover="{ scale: 1.2 }" />
<!-- As variants --> <motion.div whileHover="hovered" />
onHoverStart
当指针开始悬停在组件上时触发的回调函数。提供触发的 PointerEvent
。
<motion.div @hoverStart="(event) => console.log(event)" />
onHoverEnd
当指针停止悬停在组件上时触发的回调函数。提供触发的 PointerEvent
。
<motion.div @hoverEnd="(event) => console.log(event)" />
Press(按下)
whilePress
当按下手势处于活动状态时要标记到的目标或变体。
<!-- // As target --> <motion.button :whilePress="{ scale: 0.9 }" />
<!-- // As variants --> <motion.div whilePress="tapped" />
onPressStart
当指针开始按下组件时触发的回调函数。提供触发的 PointerEvent
。
<motion.div @pressStart="(event) => console.log(event)" />
onPress
当指针停止按下组件并且指针在组件内部释放时触发的回调函数。提供触发的 PointerEvent
。
<motion.div @press="(event) => console.log(event)" />
onPressCancel
当指针停止按下组件并且指针在组件外部释放时触发的回调函数。提供触发的 PointerEvent
。
<motion.div @pressCancel="(event) => console.log(event)" />
Focus(焦点)
whileFocus
当焦点手势处于活动状态时要标记到的目标或变体。
<!-- As target --> <motion.button :whileFocus="{ outline: 'dashed #000' }" />
<!-- As variants --> <motion.div whileFocus="focused" />
Pan(平移)
onPan
当在此元素上识别到平移手势时触发的回调函数。
注意:为了使平移手势能够正确地与触摸输入一起工作,元素需要在 x/y 轴或两个轴上禁用触摸滚动,使用 touch-action
CSS 规则。
function onPan(event, info) { console.log(info.point.x, info.point.y) } <motion.div @pan="onPan" />
平移和拖动手势事件提供原始 PointerEvent
以及一个 info
对象,该对象包含以下内容的 x
和 y
点值
point
:相对于设备或页面。delta
:自上次事件以来的距离。offset
:与原始事件的距离。velocity
:指针的当前速度。
onPanStart
当平移手势开始时触发的回调函数。提供触发的 PointerEvent
和 info
。
<motion.div @panStart="(event, info) => console.log(info.delta.x)" />
onPanEnd
当平移手势结束时触发的回调函数。提供触发的 PointerEvent
和 info
。
<motion.div @panEnd="(event, info) => console.log(info.delta.x)" />
Drag(拖动)
drag
默认值:false
为此元素启用拖动。设置为 true
以在两个方向上拖动。设置为 "x"
或 "y"
以仅在特定方向上拖动。
<motion.div drag />
whileDrag
当拖动手势处于活动状态时要标记到的目标或变体。
<!-- // As target --> <motion.div drag :whileDrag="{ scale: 0.9 }" />
<!-- // As variants --> <motion.div drag whileDrag="dragging" />
dragConstraints
对可拖动区域应用约束。
设置为可选 top
、left
、right
和 bottom
值的对象,以像素为单位测量
<motion.div drag="x" :dragConstraints="{ left: 0, right: 300 }" />
或作为对另一个元素的 ref
,以使用其边界框作为可拖动约束
<script setup> import { useDomRef } from "motion-v" const constraintsRef = useDomRef() </script> <template> <motion.div ref="constraintsRef"> <motion.div drag :dragConstraints="constraintsRef" /> </motion.div> </template>
dragSnapToOrigin
默认值:false
如果为 true
,则可拖动元素在释放时将动画返回到其中心/原点。
<motion.div drag dragSnapToOrigin />
dragElastic
默认值:0.5
约束外部允许的移动程度。0
= 不移动,1
= 完全移动。
默认设置为 0.5
。也可以设置为 false
以禁用移动。
通过传递 top
/right
/bottom
/left
的对象,可以为每个约束设置单独的值。任何缺失的值都将设置为 0
。
<motion.div drag :dragConstraints="{ left: 0, right: 300 }" :dragElastic="0.2" />
dragMomentum
默认值:true
在拖动完成时,将来自平移手势的动量应用于组件。默认设置为 true
。
<motion.div drag :dragConstraints="{ left: 0, right: 300 }" :dragMomentum="false" />
dragTransition
允许你更改拖动动量过渡。当释放可拖动元素时,会启动类型为 "inertia"
的动画。动画基于你的拖动速度。此属性允许你自定义它。
<motion.div drag :dragTransition="{ bounceStiffness: 600, bounceDamping: 10 }" />
dragDirectionLock
默认值:false
将拖动方向锁定到最早检测到的方向。例如,如果在拖动手势开始之前,组件在 x
轴上的移动多于 y
轴,则在手势的剩余时间内,它将仅在 x
轴上拖动。
<motion.div drag dragDirectionLock />
dragPropagation
默认值:false
允许拖动手势传播到子组件。
<motion.div drag="x" dragPropagation />
dragControls
通常,拖动是通过按下组件并移动它来启动的。对于某些用例,例如单击视频 scrub bar 上的任意点,我们可能希望从与可拖动组件不同的组件启动拖动。
通过使用 useDragControls
创建 dragControls
钩子,我们可以将其传递到可拖动组件的 dragControls
属性中。它公开了一个 start
方法,该方法可以从其他组件上的指针事件开始拖动。
<script setup> const dragControls = useDragControls() function startDrag(event) { dragControls.start(event, { snapToCursor: true }) } </script> <template> <div @pointerDown="startDrag" /> <motion.div drag="x" :dragControls="dragControls" /> </template>
注意: 鉴于通过设置 dragControls
你正在控制启动拖动手势,因此可以通过设置 :dragListener="false"
来禁用可拖动元素作为启动器。
dragListener
确定是否从事件侦听器触发拖动手势。如果传递 dragControls
,则将其设置为 false
将确保拖动只能由控件启动,而不是由可拖动元素上的 pointerdown
事件启动。
onDrag
当在此元素上识别到拖动手势时触发的回调函数。
function onDrag(event, info) { console.log(info.point.x, info.point.y) } <motion.div drag @drag="onDrag" />
平移和拖动手势事件提供原始 PointerEvent
以及一个 info
对象,该对象包含以下内容的 x
和 y
点值
point
:相对于设备或页面。delta
:自上次事件以来的距离。offset
:与原始事件的距离。velocity
:指针的当前速度。
onDragStart
当拖动手势开始时触发的回调函数。提供触发的 PointerEvent
和 info
。
<motion.div drag @dragStart="(event, info) => console.log(info.delta.x)" />
onDragEnd
当拖动手势结束时触发的回调函数。提供触发的 PointerEvent
和 info
。
<motion.div drag @dragEnd="(event, info) => console.log(info.delta.x)" />
onDirectionLock
当拖动方向确定时触发的回调函数。
<motion.div drag dragDirectionLock @directionLock="axis => console.log(axis)" />
Viewport(视口)
whileInView
当元素在视口中时要标记到的目标或变体。
<!-- As target --> <motion.div :whileInView="{ opacity: 1 }" />
// As variants <motion.div whileInView="visible" />
inViewOptions
用于定义如何在视口中跟踪元素的可选配置。
<motion.section :whileInView="{ opacity: 1 }" :inViewOptions="{ once: true }" />
可用选项
once
:如果为true
,则元素一旦进入视口,将不再检测后续的离开/进入事件。root
:祖先可滚动元素的ref
,用于检测与其的交叉(而不是window
)。margin
:添加到视口的边距,以更改检测区域。默认为"0px"
。使用多个值来调整上/右/下/左,例如"0px -20px 0px 100px"
。amount
:元素应进入视口才能被视为“已进入”的数量。可以是"some"
、"all"
或介于0
和1
之间的数字。默认为"some"
。
onViewportEnter
当元素进入视口时触发的回调函数。提供 IntersectionObserverEntry
,其中包含交叉事件的详细信息。
<motion.div @viewportEnter="(entry) => console.log(entry.isIntersecting)" />
onViewportLeave
当元素进入视口时触发的回调函数。提供 IntersectionObserverEntry
,其中包含交叉事件的详细信息。
<motion.div @viewportLeave="(entry) => console.log(entry.intersectionRect)" />
布局
layout
默认值:false
如果为 true
,则此组件将动画化其布局的更改.
<motion.div layout />
如果设置为 "position"
或 "size"
,则分别仅动画化其位置或大小。
<motion.img layout="position" />
layoutId
如果设置,此组件将动画化其布局的更改。此外,当新元素进入 DOM 并且已存在具有匹配 layoutId
的元素时,它将从先前元素的大小/位置动画移出。
<motion.li v-for="item in items" layout> {{item.name}} <motion.div v-if="item.isSelected" layoutId="underline" /> </motion.li>
如果先前的组件保留在树中,则这两个元素将交叉淡入淡出。
layoutDependency
默认情况下,每次渲染都会检测布局更改。为了减少测量次数并提高性能,你可以传递 layoutDependency
属性。仅当此值更改时才会进行测量。
<motion.nav layout :layoutDependency="isOpen" />
layoutScroll
为了使布局动画在可滚动元素中正确工作,需要测量其滚动偏移量。出于性能原因,Motion
不会测量每个祖先的滚动偏移量。将 layoutScroll
属性添加到应测量的元素。
<motion.div layoutScroll style="{ overflow: 'scroll' }"> <motion.div layout /> </motion.div>
layoutRoot
为了使布局动画在 position: fixed
元素中正确工作,我们需要考虑页面滚动。添加 layoutRoot
以将元素标记为 position: fixed
。
<motion.div layoutRoot :style="{ position: 'fixed' }"> <motion.div layout /> </motion.div>
onLayoutAnimationStart
布局动画开始时要运行的回调。
onLayoutAnimationComplete
布局动画完成时要运行的回调。
Advanced(高级)
inherit
设置为 false
以防止组件继承或传播父变体中的更改。
custom
要传递给动态变体的自定义数据。
<script setup> const variants = { visible: (custom) => ({ opacity: 1, transition: { delay: custom * 0.2 } }) } </script> <template> <motion.ul animate="visible"> <motion.li :custom="0" :variants="variants" /> <motion.li :custom="1" :variants="variants" /> <motion.li :custom="2" :variants="variants" /> </motion.ul> </template>
transformTemplate
默认情况下,变换按 translate
、scale
、rotate
和 skew
的顺序应用。
要更改此顺序,可以将 transformTemplate
设置为一个函数,该函数接受最新的变换和生成的变换字符串,并返回一个新的变换字符串。
<!-- // Use the latest transform values --> <motion.div :style="{ x: 0, rotate: 180 }" :transformTemplate="({ x, rotate }) => `rotate(${rotate}deg) translateX(${x}px)`" />
<!-- // Or the generated transform string --> <motion.div :style="{ x: 0, rotate: 180 }" :transformTemplate="(latest, generated) => `translate(-50%, -50%) ${generated}`" />