# JavaScript API

> Play, pause, finish and cancel any motion component from JavaScript, wait for it to end, listen for its events, and control every component at once.

Source: https://www.motion-components.dev/docs/js-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

```js
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:

## 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.

```js
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. |

```js
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.

```js
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.

```js
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`.

```ts
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-tilt` and `motion-stretch` follow the pointer. Set `disabled` to 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-icon` and `motion-theme-toggle` have their own methods: `show()` and `close()` on the dialog, `open()` and `next()` on the gallery, `goTo()` and `next()` on the slider, and `flip()` on the flip card and on `motion-icon-state`. The `motion-icon-state` loading spinner is an endless loop, and `pauseAll()` holds it. Each component's page lists the full set.
- **Static:** `motion-code-inline` does not animate.
