Skip to content

Motion

There's a motion component for every HTML and SVG element, for instance <Motion tag="div"/>, <Motion tag="circle"/> etc.

These work exactly like their static counterparts, but offer props that allow you to:

  • Animate
  • Add drag, pan, hover and tap gestures
  • Respond to gestures and elements entering the viewport with animation
  • Deeply animate throughout variants

And much more.

Supported values

Value types

Motion can animate:

  • Numbers: 0, 10 etc.
  • Strings containing numbers: "0vh", "10px" etc.
  • Colors: Hex, RGB, HSLA.
  • Complex strings containing multiple numbers and/or colors (ie "10px 0 #000")

When animating to a non-animatable value like "block", this value will be set instantly. By setting this value within transitionEnd, this value will be set at the end of the animation.

html
<Motion
  class="box"
  :animate="{
    x: 30,
    backgroundColor: '#fff',
    boxShadow: '10px 10px 0 rgba(0, 0, 0, 0.2)',
    position: 'fixed',
    transitionEnd: { display: 'none' },
  }"
/>

Value type conversion

In general, values can only be animated between two of the same type (ie two px, two % etc).

However, HTML component's x, y, width, height, top, left, right and bottom values have enhanced support and can all be animated freely between different value types.

html
<Motion
  class="box"
  :initial="{
    x:'100%'
  }"
  :animate="{
    backgroundColor: '#fff',
    x:'calc(100vw - 50%)',
    boxShadow: '10px 10px 0 rgba(0, 0, 0, 0.2)',
    position: 'fixed',
  }"
/>

Additionally, any color type (hex, HSL, RGB) can animate between each other.

Transform

Transform properties are accelerated by the GPU, and therefore animate smoothly. They can be set and animated individually as:

  • Translate shortcuts: x, y, z
  • Translate: translateX, translateY, translateZ
  • Scale: scale, scaleX, scaleY
  • Rotate: rotate, rotateX, rotateY, rotateZ
  • Skew: skew, skewX, skewY
  • Perspective: transformPerspective

motion components have enhanced style props, allowing you to set them individually there, too.

html
<Motion
    class="box"
    :hover="{ scale: [null, 1.5, 1.2] }"
    :transition="{ duration: 0.5 }"
  />

SVG note: For SVG components, x and y attributes (as opposed to the transform style) can be set using attrX and attrY within animation props.

Transform origin

transform-origin has three shortcut values that can be set and animated individually:

  • originX
  • originY
  • originZ

If set as numbers, originX and Y default to a progress value between 0 and 1. originZ defaults to pixels.

html
<Motion :style="{originX:0.5}"/>

CSS variables

Motion can animate the value of CSS variables, and also read CSS variables as animation targets.

Using pre-defined CSS variables in animation

HTML motion components can animate to and from CSS variables, as long as that variable is defined on a component ancestor.

html
<Motion
  class="box"
  :animate="{
    x: '0',
    backgroundColor: 'var(--color)',
  }"
/>

Animating CSS variables

By defining and animating CSS variables, we can use a parent motion component to declaratively animate multiple DOM children.

CSS variables are also of an arbitary type, so Motion can't infer their default type. You're able to animate rotate as a number because Motion understands that it should be set as deg, whereas '--rotate' needs to be explicitly set with the unit type, e.g. '360deg'.

html
<Motion
  tag="ul"
  :initial="{
    '--rotate': '0deg',
  }"
  :animate="{
    '--rotate': '360deg',
  }"
  :transition="{
    duration: 2,
    repeat: Infinity,
  }"
>
  <li class="box" :style="{ transform: 'rotate(var(--rotate))' }" />
</Motion>

SVG line drawing

Line drawing animations can be created with many SVG elements using three special properties: pathLength, pathSpacing and pathOffset.

These are all set as a value between 0 and 1, where 1 is the measured length of the path.

Path animations are compatible with circle, ellipse, line, path, polygon, polyline and rect elements.

Performance

Motion animates values outside the Vue render cycle for increased performance.

Using MotionValues instead of state to update visual properties will also avoid re-renders.

Where possible, animate just transform values and opacity, as they are GPU-accelerated. This way, you can animate hundreds of layers on modern mobile devices.

html
// GPU accelerated (fast)
<Motion :style="{ x: 0 }" :animate="{ x: 100 }" />

// CPU drawing (slower)
<Motion :style="{ left: 0 }" :animate="{ left: 100 }" />

Server-side rendering

motion components are fully compatible with server-side rendering, meaning the initial state of a component will be reflected in the server-generated output.

html
// Server will output `translateX(100px)`
<Motion :initial="false" :animate="{ x: 100 }" />

Exceptions

The following SVG values are not currently compatible with server-side rendering: scale, rotate, pathLength, pathOffset and pathSpacing.

scale and rotate rely on the dynamic calculation of transformOrigin - originX and originY should be set as strings (either px or %) to support these server side.

html
<Motion
  tag="circle"
  :style="{ scale: 2, originX: '100px', originY: '100px' }"
/>

path values rely on the measurement of the overall path length. Setting strokeDasharray to "0 1" will hide the path until Motion can measure it.

html
<Motion tag="path" strokeDasharray="0 1" />

Props

tag

  • default: div

Define a HTML or SVG tag to render.

html
<Motion tag="button">Button</Motion>

animate

A target of values to animate to.

html
// As values
<Motion :animate="{ opacity: 1 }" />

// As variant
<Motion animate="visible" :variants="variants" />

// Multiple variants
<Motion :animate="["visible", "active"]" :variants="variants" />

// AnimationControls
<Motion :animate="animation" />

Whenever a value in animate changes, the element will automatically animate to the latest value.

The animate prop accepts all the same values and keyframes as Motion One's animate function.

initial

A target of values to animate from when the element is first rendered.

ts
// As values
<Motion :initial="{ opacity: 1 }" />

// As variant
<Motion initial="visible" :variants="variants" />

// Multiple variants
<Motion :initial="["visible", "active"]" :variants="variants" />

// As false (disable mount animation)
<Motion :initial="false" :animate="{opacity:0}" />

If set to false, the target defined in animate will be immediately set when the element is first rendered. Only subsequent changes to animate will animate.

exit

A target of values to animate to when the element is hidden via v-if or v-show.

The element must be a direct child of the Presence component.

vue
<script setup lang="ts">
import { Motion, Presence } from "@oku-ui/motion"
const show = ref(true)
</script>

<template>
  <div class="container">
    <Presence>
      <Motion
        v-show="show"
        :initial="{ opacity: 0, scale: 0 }"
        :animate="{ opacity: 1, scale: 1 }"
        :exit="{ opacity: 0, scale: 0.6 }"
        class="box"
      />
    </Presence>
    <button @click="show = !show">
      Toggle
    </button>
  </div>
</template>

Note: This animation is only interruptible if the element is hidden via v-show.

The exit prop accepts all the same values and keyframes as Motion One's animate function.

transition

Provides a default transition for all animations to use.

html
<Motion :animate="{ x: 100 }" :transition="{ duration: 0.5 }" />

Supports all animate options.

The transition defined in this prop can be overridden for specific animation props by passing those a transition option:

Gestures

Motion extends the basic set of event listeners provided by Vue with a simple yet powerful set of UI gesture recognisers.

Learn more

Events

The Motion components emit custom DOM events to the rendered element. The detail prop is provided data on the related animation.

vue
<script setup lang="ts">
import { Motion } from "@oku-ui/motion"

const logStart = ({ detail }) => console.log("Start: ", detail)
const logComplete = ({ detail }) => console.log("Complete: ", detail)
</script>

<template>
  <Motion
    :initial="{ opacity: 0 }"
    :animate="{ opacity: 1 }"
    @motionstart="logStart"
    @motioncomplete="logComplete"
  />
</template>

motionstart

Fires when any animation is started.

motioncomplete

Fires when any animation is completed.