JavaScript API

Every component is an HTML element, so you control it the way you control any element: find it, call a method, listen for an event.

Quick start

const reveal = document.querySelector('motion-reveal')

reveal.play()        // start, or resume after pause()
reveal.pause()       // hold where it is
reveal.finish()      // jump to the end
reveal.cancel()      // stop and go back to the start
reveal.replay()      // cancel, then play

reveal.playState     // 'idle' | 'running' | 'paused' | 'finished'
await reveal.finished   // wait for the current run to end

Try the methods on a ticker:

play() starts or resumes pause() holds in place finish() jumps to the end cancel() resets to the start replay() plays again playState says where it is finished waits for the end
playState: –

Methods and properties

Member Type Default Description
play() () => Promise<void> — Starts the animation, or resumes it after pause(). Does nothing while it runs. The promise resolves when the run ends.
pause() () => void — Holds the animation where it is. play() continues it.
finish() () => void — Jumps to the end state.
cancel() () => void — Stops the animation and returns to the start state.
replay() () => void — Cancels, then plays. It means the same on every component that has it. Available on components with an animation that ends, such as the reveals, text effects, charts, icons and motion-code.
playState PlaybackState 'idle' 'idle', 'running', 'paused' or 'finished'.
finished Promise<void> — Resolves when the current run ends, by finishing or by cancel(). Each run gets a new promise. It never rejects.

Some components add their own method. motion-glitch has glitch(), which fires one extra burst. motion-underline, motion-marker, motion-strike and motion-ring have sweep(), which erases the drawn mark from its start and draws it again. A sweep is a normal run, so it fires the same events and resolves the same promises.

Wait for an animation

play() returns a promise, so you can run code after an animation ends.

Triggered from JavaScript
–
const reveal = document.querySelector('motion-reveal')

reveal.cancel()
await reveal.play()
console.log('reveal complete')

An endless effect, such as motion-ticker, motion-circle or any component with loop, never ends by itself. Its promise resolves only when you call finish() or cancel(), so don't await it.

Events

Components fire these events. They bubble and cross shadow roots, so one listener on document hears every component on the page.

Event Type Default Description
motion-start CustomEvent — A run starts, from its trigger or from play().
motion-finish CustomEvent — A run reaches its end, or finish() jumps there.
motion-cancel CustomEvent — cancel() stops a run and resets it.
motion-change CustomEvent — A widget changes state. event.detail holds the new state: { index } on motion-slider and motion-gallery, { flipped } on motion-flip-card, { position } on motion-image-compare and { active } on motion-icon-state.
motion-open CustomEvent — motion-dialog or the motion-gallery lightbox opens. On the gallery, event.detail is { index }.
motion-close CustomEvent — motion-dialog or the motion-gallery lightbox closes.
document.addEventListener('motion-finish', (event) => {
  console.log(event.target.localName, 'finished')
})

Some components contain others. motion-gallery uses motion-stagger and motion-hover, so a listener on the gallery also hears the stagger's events. When you listen on an element that contains other components, check event.target first. This matters most for motion-change: a flip card inside a slider fires one too, and it bubbles up to the slider.

const card = document.querySelector('#card')

card.addEventListener('motion-finish', (event) => {
  if (event.target !== card) return
  console.log('the card finished')
})

Control every component at once

Import these helpers to pause, resume or cancel every component on the page, or only the ones inside an element you pass.

typing along…
import { pauseAll, resumeAll } from 'motion-components'

// Pause everything while the tab is hidden
document.addEventListener('visibilitychange', () => {
  document.hidden ? pauseAll() : resumeAll()
})

// Or only the components inside one element
pauseAll(document.querySelector('#hero'))
Export Type Default Description
pauseAll(root?) (root?: Node) => void document Pauses every running component inside root, and disables the components that follow the pointer. It also holds endless decorative loops, such as the motion-icon-state loading spinner.
resumeAll(root?) (root?: Node) => void document Resumes what pauseAll() paused, and enables only what it disabled.
cancelAll(root?) (root?: Node) => void document Cancels every component inside root.

pauseAll() also reaches components inside other components, such as the stagger inside a gallery.

TypeScript

Each component registers its tag, so querySelector returns the right type without a cast. Use MotionControllable for code that works with any component.

Events are typed too, so addEventListener knows the type of event.detail. The detail types are exported: MotionChangeDetail, SliderChangeDetail, GalleryIndexDetail, FlipCardChangeDetail, ImageCompareChangeDetail and IconStateChangeDetail.

import type { MotionControllable, PlaybackState } from 'motion-components'

const reveal = document.querySelector('motion-reveal')   // MotionReveal | null

function restart(el: MotionControllable) {
  el.cancel()
  void el.play()
}

const state: PlaybackState | undefined = reveal?.playState

const slider = document.querySelector('motion-slider')
slider?.addEventListener('motion-change', (event) => {
  console.log(event.detail)   // MotionChangeDetail
})

Reduced motion

When the visitor asks for reduced motion, play() shows the end state at once. It still resolves, and it still fires motion-start and motion-finish, so your code needs no special case. motion-words and motion-countdown keep running without animation, because changing words and counting are their job.

Scroll-driven components

motion-parallax, motion-scene, motion-progress and motion-blur follow the scroll position instead of a clock. playState is 'running' while they follow it. pause() stops following and holds the current state, play() follows again, finish() shows the end state, and cancel() stops and resets.

Components without these methods