---
area: "Learn"
parent: "Motion chapter"
url: "https://unpoly.com/learn/controlling-motion"
---
<nav aria-label="Unpoly docs">[All docs](https://unpoly.com/index.md) · [Motion chapter](https://unpoly.com/learn/motion.md)</nav>

# Controlling motion

Every [transition](https://unpoly.com/learn/transitions.md) and [animation](https://unpoly.com/learn/animations.md) plays with a duration and easing that you can change per element or for the whole app. Users who prefer reduced motion see no motion, and you can turn them off yourself. When your code needs the final state, wait for running motion effects to end or finish them right away.

<nav aria-label="Contents">

#### Contents

- [Duration and easing](#duration-and-easing)
- [Reduced motion and disabling motion](#disabling-animation-globally)
- [Running code after a motion effect](#running-code-after-an-animation)
- [Finishing running motion effects](#finishing-running-animations)

</nav>

## Duration and easing {#duration-and-easing}

### Changing the duration {#duration}

Motion effects take 175 milliseconds by default. To change the duration for one element, set an `[up-duration]` attribute:

```html
<a href="/users" up-target=".list" up-transition="cross-fade" up-duration="400">Show users</a>
```

From JavaScript, pass a `{ duration }` option to [`up.render()`](https://unpoly.com/api/up.render.md), [`up.animate()`](https://unpoly.com/api/up.animate.md) or any other function that animates:

```js
up.animate('.warning', 'fade-in', { duration: 400 })
```

To change the default for all motion effects, set [`up.motion.config.duration`](https://unpoly.com/api/up.motion.config.md#config.duration):

```js
up.motion.config.duration = 300
```

Overlays have their own defaults for opening and closing, which you can set per [mode](https://unpoly.com/learn/overlays.md#layer-modes). See [`up.layer.config`](https://unpoly.com/api/up.layer.config.md) for options like [`up.layer.config.overlay.openDuration`](https://unpoly.com/api/up.layer.config.md#config.overlay.openDuration).

### Changing the easing {#easing}

The _easing_ is a [timing function](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function) that controls how a motion effect accelerates. The default is `ease`. To change it for one element, set an `[up-easing]` attribute:

```html
<a href="/users" up-target=".list" up-transition="cross-fade" up-easing="ease-in-out">Show users</a>
```

From JavaScript, pass an `{ easing }` option:

```js
up.animate('.warning', 'fade-in', { easing: 'linear' })
```

Any CSS timing function works, including `cubic-bezier()` curves. To change the default for all motion effects, set [`up.motion.config.easing`](https://unpoly.com/api/up.motion.config.md#config.easing).

## Reduced motion and disabling motion {#disabling-animation-globally}

Motion is enabled unless the user has asked their system to [minimize non-essential motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion). In that case [`up.motion.config.enabled`](https://unpoly.com/api/up.motion.config.md#config.enabled) is `false`. When [logging](https://unpoly.com/api/up.log.enable.md) is enabled, Unpoly prints _Animations are disabled_ to the console when it boots.

With motion disabled, a motion effect jumps to its last frame instantly. The page ends up in the same state either way, so your code never needs to check the setting.

To disable motion for all users, set the option yourself:

```js
up.motion.config.enabled = false
```

This is useful in automated integration tests, where a test might otherwise assert on the page while an element is still fading in. It also removes a source of [flaky tests](https://makandracards.com/makandra/47336-fixing-flaky-integration-tests).

To force-enable motion even for users who prefer reduced motion, set `up.motion.config.enabled = true`.

To skip a single motion effect while others keep playing, pass `none` or `false` as the animation or transition.

## Running code after a motion effect {#running-code-after-an-animation}

The promise returned by [`up.render()`](https://unpoly.com/api/up.render.md) fulfills when the new fragment is in the DOM, while its transition may still be playing. To run code after all motion effects have ended, await the [`up.RenderJob#finished`](https://unpoly.com/api/up.RenderJob.prototype.finished.md) promise, or pass an `{ onFinished }` callback:

```js
let result = await up.render({ target: '.list', url: '/users', transition: 'cross-fade' }).finished
```

An overlay's `{ onOpened }` callback runs while the opening animation may still be playing. To be called after the animation, pass `{ onFinished }` to [`up.layer.open()`](https://unpoly.com/api/up.layer.open.md).

[`up.destroy()`](https://unpoly.com/api/up.destroy.md) keeps the element in the DOM while its exit animation plays. To run code after the element was removed, pass `{ onFinished }`:

```js
up.destroy('.flash', {
  animation: 'fade-out',
  onFinished() { console.log('Flash removed') }
})
```

See [Render lifecycle: Awaiting postprocessing](https://unpoly.com/learn/render-lifecycle.md#postprocessing) for everything that may still change a fragment after rendering.

## Finishing running motion effects {#finishing-running-animations}

To complete all motion effects on the screen, call [`up.motion.finish()`](https://unpoly.com/api/up.motion.finish.md). Every running motion effect jumps to its last frame and its promise settles:

```js
up.motion.finish()
```

Pass an element to only finish motion effects on that element and its children:

```js
up.motion.finish('.list')
```

Unpoly also finishes motion effects by itself when they would collide. When you animate an element that is already animating, the running one is completed before the new one starts. The same happens when a fragment is updated again while its transition is still playing.

<nav aria-label="Reading order">

- [Previous: Animations](https://unpoly.com/learn/animations.md)
- [Next: Scripting](https://unpoly.com/learn/scripting.md)

</nav>

