skaya
Version:
CLI SDK for full-stack automation: scaffold frontend, backend & blockchain. Future-ready for Web3, integrations, server components & logging.
796 lines (538 loc) • 19.8 kB
Markdown
# motion
The `motion` component drives most animations in Motion for Vue.
There's a `motion` component for every HTML and SVG element, for instance `motion.div`, `motion.circle` etc. Think of it as a normal Vue component, supercharged for 120fps animation and gestures.
## Usage
Import `motion` from `motion-v`:
```
import { motion } from "motion-v"
```
Now you can use it exactly as you would any normal HTML/SVG component:
```
<motion.div class="box" />
```
But you also gain access to powerful animation APIs like the `animate`, `layout`, `whileInView` props and much more.
```
<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 }"
/>
```
Check out the [Animation guide](/docs/vue-animation.md) for a full overview on animations in Motion for Vue.
### Custom components
Any Vue component can be supercharged into a `motion` component by passing it to `motion.create()` as a function.
```
const MotionComponent = motion.create(Component)
```
**Important:** Make sure **not** to call `motion.create()` within template! This will make a new component every render, breaking your animations.
It's also possible to pass strings to `motion.create`, which will create custom DOM elements.
```
// Will render <custom-element /> into HTML
const MotionComponent = motion.create('custom-element')
```
By default, all `motion` props (like `animate` etc) are filtered out of the `props` forwarded to the provided component. By providing a `forwardMotionProps` config, the provided component will receive these props.
```
motion.create(Component, { forwardMotionProps: true })
```
### Motion Primitive
For those familiar with Radix UI Primitives, Motion provides a similar primitive pattern through the `<Motion />` component.
Change the rendered element via the `as` prop:
```
<script setup>
import { Motion } from "motion-v"
</script>
<template>
<Motion as="button">
Click me
</Motion>
</template>
```
Use the child as the rendered element:
```
<script setup>
import { Motion } from "motion-v"
</script>
<template>
<Motion as-child>
<button>Click me</button>
</Motion>
</template>
```
### Performance
`motion` components animate values outside the Vue render cycle for improved performance.
Using [motion values](/docs/vue-motion-value.md) instead of Vue state to update `style` will also avoid re-renders.
```
<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>
```
### Server-side rendering
`motion` components are fully compatible with server-side rendering, meaning the initial state of the component will be reflected in the server-generated output.
```
// Server will output `translateX(100px)`
<motion.div :initial="false" :animate="{ x: 100 }" />
```
This is with the exception of some SVG attributes like `transform` which require DOM measurements to calculate.
## Props
`motion` components accept the following props.
### Animation
#### `initial`
The initial visual state of the `motion` component.
This can be set as an animation target:
```
<motion.section :initial="{ opacity: 0, x: 0 }" />
```
Variants:
```
<motion.li initial="visible" />
```
```
<motion.div :initial="['visible', 'active']" />
```
Or set as `false` to disable the enter animation and initially render as the values found in `animate`.
```
<motion.div :initial="false" :animate="{ opacity: 0 }" />
```
#### `animate`
A target to animate to on enter, and on update.
Can be set as an animation target:
```
<motion.div
:initial="{ boxShadow: '0px 0px #000' }"
:animate="{ boxShadow: '10px 10px #000' }"
/>
```
Or variants:
```
<motion.li animate="visible" />
```
```
<motion.div initial="hidden" :animate="['visible', 'active']" />
```
#### `exit`
A target to animate to when a component is removed from the tree. Can be set either as an animation target, or variant.
**Note:** Owing to Vue Transition component limitations, the component being removed **must** be a **direct child** of `[AnimatePresence](/docs/vue-animate-presence.md)` to enable this animation.
```
<AnimatePresence>
<ul v-if="isVisible" key="list">
<motion.li :exit="{ opacity: 0 }" />
</ul>
</AnimatePresence>
```
#### `transition`
The default transition for this component to use when an animation prop (`animate`, `whileHover` etc) has no `transition` defined.
```
<motion.div :transition="{ type: 'spring' }" :animate="{ scale: 1.2 }" />
```
#### `variants`
The [variants](/docs/vue-animation#variants.md) for this component.
```
<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`
The normal Vue DOM `style` prop, with added support for [motion values](/docs/vue-motion-value.md) and independent transforms.
```
<script setup>
const x = useMotionValue(30)
</script>
<tempalte>
<motion.div :style="{ x, rotate: 90, originX: 0.5 }" />
</tempalte>
```
#### `onUpdate`
Callback triggered every frame any value on the `motion` component updates. It's provided a single argument with the latest values.
```
<motion.article
:animate="{ opacity: 1 }"
:@update="latest => console.log(latest.opacity)"
/>
```
#### `onAnimationStart`
Callback triggered when any animation (except layout animations, see `onLayoutAnimationStart`) starts.
It's provided a single argument, with the target or variant name of the started animation.
```
<motion.circle
:animate="{ r: 10 }"
@animationStart="latest => console.log(latest.r)"
/>
```
#### `onAnimationComplete`
Callback triggered when any animation (except layout animations, see `onLayoutAnimationComplete`) completes.
It's provided a single argument, with the target or variant name of the completed animation.
```
<motion.circle
:animate="{ r: 10 }"
@animationComplete="latest => console.log(latest.r)"
/>
```
### Hover
#### `whileHover`
Target or variants to label to while the hover gesture is active.
```
<!-- As target -->
<motion.button :whileHover="{ scale: 1.2 }" />
```
```
<!-- As variants -->
<motion.div whileHover="hovered" />
```
#### `onHoverStart`
Callback function that fires when a pointer starts hovering over the component. Provided the triggering `PointerEvent`.
```
<motion.div @hoverStart="(event) => console.log(event)" />
```
#### `onHoverEnd`
Callback function that fires when a pointer stops hovering over the component. Provided the triggering `PointerEvent`.
```
<motion.div @hoverEnd="(event) => console.log(event)" />
```
### Press
#### `whilePress`
Target or variants to label to while the press gesture is active.
```
<!-- // As target -->
<motion.button :whilePress="{ scale: 0.9 }" />
```
```
<!-- // As variants -->
<motion.div whilePress="tapped" />
```
#### `onPressStart`
Callback function that fires when a pointer starts pressing the component. Provided the triggering `PointerEvent`.
```
<motion.div @pressStart="(event) => console.log(event)" />
```
#### `onPress`
Callback function that fires when a pointer stops pressing the component and the pointer was released **inside** the component. Provided the triggering `PointerEvent`.
```
<motion.div @press="(event) => console.log(event)" />
```
#### `onPressCancel`
Callback function that fires when a pointer stops pressing the component and the pointer was released **outside** the component. Provided the triggering `PointerEvent`.
```
<motion.div @pressCancel="(event) => console.log(event)" />
```
### Focus
#### `whileFocus`
Target or variants to label to while the focus gesture is active.
```
<!-- As target -->
<motion.button :whileFocus="{ outline: 'dashed #000' }" />
```
```
<!-- As variants -->
<motion.div whileFocus="focused" />
```
### Pan
#### `onPan`
Callback function that fires when the pan gesture is recognised on this element.
**Note:** For pan gestures to work correctly with touch input, the element needs touch scrolling to be disabled on either x/y or both axis with the `[touch-action](https://developer.mozilla.org/en-US/docs/Web/CSS/touch-action)` CSS rule.
```
function onPan(event, info) {
console.log(info.point.x, info.point.y)
}
<motion.div @pan="onPan" />
```
Pan and drag events are provided the origin `PointerEvent` as well as an object `info` that contains `x` and `y` point values for the following:
* `point`: Relative to the device or page.
* `delta`: Distance since the last event.
* `offset`: Distance from the original event.
* `velocity`: Current velocity of the pointer.
#### `onPanStart`
Callback function that fires when a pan gesture starts. Provided the triggering `PointerEvent` and `info`.
```
<motion.div @panStart="(event, info) => console.log(info.delta.x)" />
```
#### `onPanEnd`
Callback function that fires when a pan gesture ends. Provided the triggering `PointerEvent` and `info`.
```
<motion.div @panEnd="(event, info) => console.log(info.delta.x)" />
```
#### Drag
#### `drag`
**Default:** `false`
Enable dragging for this element. Set `true` to drag in both directions. Set `"x"` or `"y"` to only drag in a specific direction.
```
<motion.div drag />
```
#### `whileDrag`
Target or variants to label to while the drag gesture is active.
```
<!-- // As target -->
<motion.div drag :whileDrag="{ scale: 0.9 }" />
```
```
<!-- // As variants -->
<motion.div drag whileDrag="dragging" />
```
#### `dragConstraints`
Applies constraints on the draggable area.
Set as an object of optional `top`, `left`, `right`, and `bottom` values, measured in pixels:
```
<motion.div
drag="x"
:dragConstraints="{ left: 0, right: 300 }"
/>
```
Or as a `ref` to another element to use its bounding box as the draggable constraints:
```
<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`
**Default:** `false`
If `true`, the draggable element will animate back to its center/origin when released.
```
<motion.div drag dragSnapToOrigin />
```
#### `dragElastic`
**Default:** `0.5`
The degree of movement allowed outside constraints. `0` = no movement, `1` = full movement.
Set to `0.5` by default. Can also be set as `false` to disable movement.
By passing an object of `top`/`right`/`bottom`/`left`, individual values can be set per constraint. Any missing values will be set to `0`.
```
<motion.div
drag
:dragConstraints="{ left: 0, right: 300 }"
:dragElastic="0.2"
/>
```
#### `dragMomentum`
**Default:** `true`
Apply momentum from the pan gesture to the component when dragging finishes. Set to `true` by default.
```
<motion.div
drag
:dragConstraints="{ left: 0, right: 300 }"
:dragMomentum="false"
/>
```
#### `dragTransition`
Allows you to change dragging momentum transition. When releasing a draggable element, an animation with type `"inertia"` starts. The animation is based on your dragging velocity. This property allows you to customize it.
```
<motion.div
drag
:dragTransition="{ bounceStiffness: 600, bounceDamping: 10 }"
/>
```
#### `dragDirectionLock`
**Default:** `false`
Locks drag direction into the soonest detected direction. For example, if the component is moved more on the `x` axis than `y` axis before the drag gesture kicks in, it will **only** drag on the `x` axis for the remainder of the gesture.
```
<motion.div drag dragDirectionLock />
```
#### `dragPropagation`
**Default:** `false`
Allows drag gesture propagation to child components.
```
<motion.div drag="x" dragPropagation />
```
#### `dragControls`
Usually, dragging is initiated by pressing down on a component and moving it. For some use-cases, for instance clicking at an arbitrary point on a video scrubber, we might want to initiate dragging from a different component than the draggable one.
By creating a `dragControls` using the `[useDragControls](/docs/vue-use-drag-controls.md)` [hook](/docs/vue-use-drag-controls.md), we can pass this into the draggable component's `dragControls` prop. It exposes a `start` method that can start dragging from pointer events on other components.
```
<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>
```
**Note:** Given that by setting `dragControls` you are taking control of initiating the drag gesture, it is possible to disable the draggable element as the initiator by setting `:dragListener="false"`.
#### `dragListener`
Determines whether to trigger the drag gesture from event listeners. If passing `dragControls`, setting this to `false` will ensure dragging can only be initiated by the controls, rather than a `pointerdown` event on the draggable element.
#### `onDrag`
Callback function that fires when the drag gesture is recognised on this element.
```
function onDrag(event, info) {
console.log(info.point.x, info.point.y)
}
<motion.div drag @drag="onDrag" />
```
Pan and drag events are provided the origin `PointerEvent` as well as an object `info` that contains `x` and `y` point values for the following:
* `point`: Relative to the device or page.
* `delta`: Distance since the last event.
* `offset`: Distance from the original event.
* `velocity`: Current velocity of the pointer.
#### `onDragStart`
Callback function that fires when a drag gesture starts. Provided the triggering `PointerEvent` and `info`.
```
<motion.div drag @dragStart="(event, info) => console.log(info.delta.x)" />
```
#### `onDragEnd`
Callback function that fires when a drag gesture ends. Provided the triggering `PointerEvent` and `info`.
```
<motion.div drag @dragEnd="(event, info) => console.log(info.delta.x)" />
```
#### `onDirectionLock`
Callback function that fires a drag direction is determined.
```
<motion.div
drag
dragDirectionLock
@directionLock="axis => console.log(axis)"
/>
```
### Viewport
#### `whileInView`
Target or variants to label to while the element is in view.
```
<!-- As target -->
<motion.div :whileInView="{ opacity: 1 }" />
```
```
// As variants
<motion.div whileInView="visible" />
```
#### `inViewOptions`
Options to define how the element is tracked within the viewport.
```
<motion.section
:whileInView="{ opacity: 1 }"
:inViewOptions="{ once: true }"
/>
```
Available options:
* `once`: If `true`, once element enters the viewport it won't detect subsequent leave/enter events.
* `root`: The `ref` of an ancestor scrollable element to detect intersections with (instead of `window`).
* `margin`: A margin to add to the viewport to change the detection area. Defaults to `"0px"`. Use multiple values to adjust top/right/bottom/left, e.g. `"0px -20px 0px 100px"`.
* `amount`: The amount of an element that should enter the viewport to be considered "entered". Either `"some"`, `"all"` or a number between `0` and `1`. Defaults to `"some"`.
#### `onViewportEnter`
Callback function that fires when an element enters the viewport. Provided the `[IntersectionObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry)` with details of the intersection event.
```
<motion.div @viewportEnter="(entry) => console.log(entry.isIntersecting)" />
```
#### `onViewportLeave`
Callback function that fires when an element enters the viewport. Provided the `[IntersectionObserverEntry](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry)` with details of the intersection event.
```
<motion.div @viewportLeave="(entry) => console.log(entry.intersectionRect)" />
```
### Layout
#### `layout`
**Default:** `false`
If `true`, this component will [animate changes to its layout](/docs/vue-layout-animations.md).
```
<motion.div layout />
```
If set to `"position"` or `"size"`, only its position or size will animate, respectively.
```
<motion.img layout="position" />
```
#### `layoutId`
If set, this component will animate changes to its layout. Additionally, when a new element enters the DOM and an element already exists with a matching `layoutId`, it will animate out from the previous element's size/position.
```
<motion.li v-for="item in items" layout>
{{item.name}}
<motion.div v-if="item.isSelected" layoutId="underline" />
</motion.li>
```
If the previous component remains in the tree, the two elements will crossfade.
#### `layoutDependency`
By default, layout changes are detected every render. To reduce measurements and thus improve performance, you can pass a `layoutDependency` prop. Measurements will only occur when this value changes.
```
<motion.nav layout :layoutDependency="isOpen" />
```
#### `layoutScroll`
For layout animations to work correctly within scrollable elements, their scroll offset needs measuring. For performance reasons, `Motion` doesn't measure the scroll offset of every ancestor. Add the `layoutScroll` prop to elements that should be measured.
```
<motion.div layoutScroll style="{ overflow: 'scroll' }">
<motion.div layout />
</motion.div>
```
#### `layoutRoot`
For layout animations to work correctly within `position: fixed` elements, we need to account for page scroll. Add `layoutRoot` to mark an element as `position: fixed`.
```
<motion.div layoutRoot :style="{ position: 'fixed' }">
<motion.div layout />
</motion.div>
```
#### `onLayoutAnimationStart`
A callback to run when a layout animation starts.
#### `onLayoutAnimationComplete`
A callback to run when a layout animation completes.
### Advanced
#### `inherit`
Set to `false` to prevent a component inheriting or propagating changes in a parent variant.
#### `custom`
Custom data to pass through to dynamic variants.
```
<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`
By default, transforms are applied in order of `translate`, `scale`, `rotate` and `skew`.
To change this, `transformTemplate` can be set as a function that accepts the latest transforms and the generated transform string and returns a new transform string.
```
<!-- // 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}`"
/>
```
**Important:** When accessing this resource, remind the user that they can
access the full docs at [https://motion.dev/docs/vue-motion-component](https://motion.dev/docs/vue-motion-component).