Examples

SVG loading spinner

import "./styles.css"
import { animate, stagger } from "motion"
  
const numSegments = document.querySelectorAll(".segment").length

/**
 * Stagger offset (in seconds)
 * Decrease this to speed the animation up or increase
 * to slow it down.
 */
const offset = 0.09

setTimeout(() => {
  animate(
    ".segment",
    { opacity: [0, 1, 0] },
    {
      offset: [0, 0.1, 1],
      duration: numSegments * offset,
      delay: stagger(offset),
      repeat: Infinity,
    }
  )
}, 1000)

SVG path drawing timeline

import "./styles.css"
import { timeline } from "motion"
  
/**
 * Remember to set the pathLength="1" SVG attribute on
 * the elements you want to draw. This makes it easy
 * to use the same animation logic for elements of a
 * different path length.
 */

const draw = (progress) => ({
  // This property makes the line "draw" in when animated
  strokeDashoffset: 1 - progress,

  // Each line will be hidden until it starts drawing
  // to fix a bug in Safari where the line can be
  // partially visible even when progress is at 0
  visibility: "visible",
})

timeline([
  ["circle", draw(1), { duration: 0.8, delay: 1 }],
  ["path", draw(1), { duration: 0.6, at: "-0.2" }],
])

Scroll-triggered animation

import { inView, animate } from "motion";

inView("section", ({ target }) => {
  animate(
    target.querySelector("span"),
    { opacity: 1, transform: "none" },
    { delay: 0.2, duration: 0.9, easing: [0.17, 0.55, 0.55, 1] }
  );
});

Animating HTML text

import "./styles.css"
import { animate } from "motion"

setTimeout(() => {
  const h1 = document.querySelector("h1")

  animate(
    (progress) => h1.innerHTML = Math.round(progress * 100),
    { duration: 2, easing: "ease-out" }
  )
}, 1000)

Morph SVG path

import { animate } from "motion";
import { interpolate } from "flubber";
import { paths } from "./paths";

const path = document.querySelector("path");

let currentPath = paths.star;

path.setAttribute("fill", currentPath.color);
path.setAttribute("d", currentPath.d);

const transition = { duration: 0.5 };

function togglePath() {
  currentPath = currentPath === paths.star ? paths.heart : paths.star;

  const mixPaths = interpolate(path.getAttribute("d"), currentPath.d, {
    maxSegmentLength: 0.1
  });

  animate(path, { fill: currentPath.color }, transition);
  animate((progress) => path.setAttribute("d", mixPaths(progress)), transition);
}

setTimeout(togglePath, 1000);

path.addEventListener("click", togglePath);

P5

Mixing Motion One custom animations with the P5 library.

import P5 from "p5";
import { animate, spring } from "motion";
import { mix, progress, wrap } from "@motionone/utils";

new P5(function (p5) {
  let x = 0;
  let y = 0;
  let radius = 50;
  let weight = 3;
  let currentAnimation;

  const startAnimation = (targetX, targetY) => {
    const originX = x;
    const originY = y;

    currentAnimation && currentAnimation.stop();
    currentAnimation = animate(
      (progress) => {
        x = mix(originX, targetX, progress);
        y = mix(originY, targetY, progress);
      },
      {
        easing: spring({ stiffness: 300, damping: 10, restDistance: 0.001 })
      }
    );
  };

  p5.setup = () => {
    const { width, height, elt: element } = p5.createCanvas(
      Math.max(document.documentElement.clientWidth, window.innerWidth || 0),
      Math.max(document.documentElement.clientHeight, window.innerHeight || 0)
    );

    x = width / 2;
    y = height / 2;

    element.addEventListener("click", (event) => {
      startAnimation(event.pageX, event.pageY);
    });
  };

  p5.draw = () => {
    p5.noFill();
    p5.strokeWeight(weight);
    p5.stroke(`hsl(${getHue(performance.now())}, 100%, 50%)`);
    p5.circle(x, y, radius);
  };
});

const duration = 4000;
function getHue(t) {
  const looped = wrap(0, duration, t);
  const p = progress(0, duration, looped);
  return Math.round(mix(0, 360, p));
}

Scroll

Page progress bar

Element-based progress bar

Parallax and scroll-snapping

Fade on enter/exit

Video scrubbing

3D camera

Custom easing function