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 endTry 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 Methods and properties
| Member | Type | Default | Description |
|---|---|---|---|
| | () => Promise<void> | — | Starts the animation, or resumes it after |
| | () => void | — | Holds the animation where it is. |
| | () => void | — | Jumps to the end state. |
| | () => void | — | Stops the animation and returns to the start state. |
| | () => 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 |
| | PlaybackState | 'idle' | |
| | Promise<void> | — | Resolves when the current run ends, by finishing or by |
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.
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 |
|---|---|---|---|
| | CustomEvent | — | A run starts, from its trigger or from |
| | CustomEvent | — | A run reaches its end, or |
| | CustomEvent | — | |
| | CustomEvent | — | A widget changes state. |
| | CustomEvent | — | |
| | CustomEvent | — | |
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.
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 |
|---|---|---|---|
| | (root?: Node) => void | document | Pauses every running component inside |
| | (root?: Node) => void | document | Resumes what |
| | (root?: Node) => void | document | Cancels every component inside |
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
- Pointer-driven:
motion-hover,motion-press,motion-magnetic,motion-tiltandmotion-stretchfollow the pointer. Setdisabledto turn them off.pauseAll()does it for you. - Widgets:
motion-dialog,motion-gallery,motion-slider,motion-flip-card,motion-image-compare,motion-spotlight,motion-icon-state,motion-theme-iconandmotion-theme-togglehave their own methods:show()andclose()on the dialog,open()andnext()on the gallery,goTo()andnext()on the slider, andflip()on the flip card and onmotion-icon-state. Themotion-icon-stateloading spinner is an endless loop, andpauseAll()holds it. Each component's page lists the full set. - Static:
motion-code-inlinedoes not animate.