# animate-presence

> Effortless element entrance/exit animations

Latest version **0.2.1** (published 2020-02-29) · MIT license · 0 weekly downloads

## Install

```sh
npm install animate-presence
pnpm add animate-presence
yarn add animate-presence
bun add animate-presence
```

## Health

**Score 30/100 (F)** — status: abandoned.

Positive: has types; esm support; no vulnerabilities; high quality score.

Warnings: low downloads; pre 1.0.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 0.2.1 |
| Published | 2020-02-29 |
| First published | 2020-02-14 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 0 |
| Unpacked size | 899.1 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 109 |
| Maintainers | natemoo-re |

## Links

- npm: https://www.npmjs.com/package/animate-presence
- Repository: https://github.com/natemoo-re/animate-presence
- Homepage: https://github.com/natemoo-re/animate-presence/packages/animate-presence#readme
- Issues: https://github.com/natemoo-re/animate-presence/issues
- npm.io page: https://npm.io/package/animate-presence

## Recent versions

- 0.2.1 (latest) — 2020-02-29
- 0.0.0-e90c470 (canary) — 2020-12-12
- 0.2.1-rc.47d491d (next) — 2020-02-29
- 0.0.0-223174c — 2020-12-12
- 0.0.0-fa24a00 — 2020-10-16
- 0.0.0-b119969 — 2020-10-07
- 0.0.0-2062c4d — 2020-09-12
- 0.0.0-b1992ae — 2020-07-19
- 0.0.0-4adf64f — 2020-03-18
- 0.0.0-5a2454a — 2020-03-04
- 0.0.0-379153f — 2020-03-04
- 0.2.1-rc.c9a43fa — 2020-02-29
- 0.0.0-0e7af75 — 2020-02-29
- 0.2.0 — 2020-02-26
- 0.1.1 — 2020-02-25
- … 1 more at https://npm.io/package/animate-presence/versions

## README

<div align="center">
    <img src="https://raw.githubusercontent.com/natemoo-re/animate-presence/master/.github/assets/logo.svg?sanitize=true" alt="Animate Presence" height="175" width="175"/>
</div>
<h3 align="center">Effortless element entrance/exit animations.</h3>

<h3 align="center">
  <a href="https://github.com/natemoo-re/animate-presence">
    <img src="https://raw.githubusercontent.com/natemoo-re/animate-presence/master/.github/assets/logo-github.svg?sanitize=true" alt="GitHub" height="24"/>
  </a>
  &nbsp;/&nbsp;
  <a href="https://www.npmjs.com/package/animate-presence">
    <img src="https://raw.githubusercontent.com/natemoo-re/animate-presence/master/.github/assets/logo-npm.svg?sanitize=true" alt="NPM" height="24"/>
  </a>
  &nbsp;/&nbsp;
  <a href="https://animate-presence.now.sh/">
    <img src="https://raw.githubusercontent.com/natemoo-re/animate-presence/master/.github/assets/learn.svg?sanitize=true" alt="Examples" height="24"/>
  </a>
</h3>

<br />

---

Unlike most animation libraries, there's no new API to learn&mdash;just use CSS or the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API).

Here's a basic example using CSS. See [Declarative Animations](#declarative-animations-css).

```html
<animate-presence>
  <div class="item">Item A</div>
  <div class="item">Item B</div>
  <div class="item">Item C</div>
</animate-presence>

<style>
  .item[data-enter] {
    animation: fade 1s ease-in;
  }
  .item[data-exit] {
    animation: fade 1s ease-out reverse;
    animation-fill-mode: both;
  }
  @keyframes fade {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
</style>
```

Here's a basic example using WAAPI. See [Imperative Animations](#imperative-animations-waapi).

```jsx
import { createPresenceHandler } from 'animate-presence';

const List = () => {
  const fade = new Animation({
    opacity: [0, 1],
  });
  const handleEnter = createPresenceHandler(async el => {
    return el.animate(fade, {
      duration: 1000,
      easing: 'ease-in',
      fill: 'both',
    });
  });
  const handleExit = createPresenceHandler(async el => {
    return el.animate(fade, {
      duration: 1000,
      easing: 'ease-out',
      direction: 'reverse',
      fill: 'both',
    });
  });
  return (
    <animate-presence
      onAnimatePresenceEnter={handleEnter}
      onAnimatePresenceExit={handleExit}
    >
      <div class="item">Item A</div>
      <div class="item">Item B</div>
      <div class="item">Item C</div>
    </animate-presence>
  );
};
```

## Declarative Animations (CSS)

As child elements are added or removed, `data-enter` and `data-exit` attributes are automatically applied.

Using CSS, you can use these attributes as hooks to apply an animation or transition.

Once the animation finishes, `<animate-presence>` automatically cleans itself up, removing the attribute and any listeners.

### Attributes

| Attribute    | Description                                                                                                                    |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------ |
| `data-enter` | Applied immediately when a node enters. Trigger your entrance animation here.                                                  |
| `data-exit`  | Applied when a node will be removed. Trigger your exit animations here. The node will be removed when the animation completes. |

> **Note** Animate Presence overrides the default `animation-fill-mode` of animating children to `both` (rather than `none`).
> This provides more reasonable default behavior in most situations, so the rule is injected with a [specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity) (`021`) high enough to override the `animation` shorthand for most rules (like `.item[data-enter]`).

> If you experience visual flickering at the start or end of an animation, you may be using a selector with a higher specificity than `021`.
> The `animation` shorthand resets the `animation-fill-mode` to `none`, so you likely need to manually specify `both`.

### Stagger

When multiple elements enter/exit, Animate Presence automatically sets a custom property, `--i`, on each child.

This makes staggered animations simple with a small `calc` function.

```css
[data-enter] {
  animation-delay: calc(var(--i, 0) * 50ms);
}
```

## Imperative Animations (WAAPI)

Using the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API) to imperatively
animate entrances/exits is the best way to coordinate complex, multi-step animations.

> It's also possible to use your favorite animation library, like [Popmotion](https://popmotion.io/pure/) or [Anime.js](https://animejs.com/)!

### Events

| Event                  | Description                                                                                                                  |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `animatePresenceEnter` | Dispatched immediately when a node enters. Handle your entrance animation here.                                              |
| `animatePresenceExit`  | Dispatched when a node will be removed. Handle your exit animations here. The node will be removed when the handler returns. |

As you might expect, these events are dispatched to the children of `animate-presence` as they enter or exit.
These events bubble, so listeners can be attached directly to an `animate-presence` element via `addEventListener` or `onAnimatePresenceEnter/Exit` handlers.

### `createPresenceHandler`

Use the included `createPresenceHandler` utility to create async callbacks for these events.

```js
import { createPresenceHandler } from 'animate-presence';

const onExit = createPresenceHandler(async (el, { i }) => {
  // Note that `element.animate()` doesn't return a Promise, it returns an `Animation`.
  // In order to await the `Animation`, you want `Animation.finished`.
  await el.animate(
    [{ transform: 'translateY(0)' }, { transform: 'translateY(50%)' }],
    {
      duration: 300,
      delay: i * 50,
      easing: 'cubic-bezier(0.165, 0.84, 0.44, 1)',
      fill: 'both', // Be sure to set this!
    }
  ).finished;

  // `createPresenceHandler` can optionally `return el.animate()` instead of using `await el.animate().finished`
  await el.animate(
    [
      { opacity: 1, transform: `translate(0, 50%)` },
      { opacity: 0, transform: `translate(16px, 50%)` },
    ],
    {
      duration: 300,
      easing: 'cubic-bezier(0.165, 0.84, 0.44, 1)',
      fill: 'both', // Be sure to set this!
    }
  ).finished;
});

const ap = document.querySelector('animate-presence');
ap.addEventListener('animatePresenceExit', onExit);
```

## Nesting

Animate Presence uses a tree-based approach, meaning that nested `animate-presence` elements are aware of their parents and children.

**Enter** animations are applied top-down, meaning the top-level parent enters, which triggers the next child entrance, and so on.

**Exit** animations are applied bottom-up, meaning the deepest child exits, which triggers the next parent exit, and so on.

> Animate Presence relies on `querySelectorAll` to construct the internal tree, so it does not (yet) work with Shadow Roots.
> This is an area I'm actively looking into.

## Usage with [@stencil/router](https://github.com/ionic-team/stencil-router)

For Stencil apps using `@stencil/router`, Animate Presence has an `<animated-route-switch>` component which allows you to smoothly animate between routes.

1. Swap your `<stencil-route-switch>` component with `<animated-route-switch>`

2. Due to a current bug in `@stencil/router`, you will need to use `injectHistory` to pass `location` down to `animated-route-switch`.

   Hopefully this will be fixed soon, but there's a simple work around for now.

```tsx
import { Component, h, Prop } from '@stencil/core';
import { injectHistory, LocationSegments } from '@stencil/router';

@Component({
  tag: 'app-root',
  styleUrl: 'app-root.css',
  scoped: true,
})
export class AppRoot {
  @Prop() location: LocationSegments;

  render() {
    return (
      <div>
        <header>
          <h1>Stencil App Starter</h1>
        </header>

        <main>
          <stencil-router>
            {/* Pass `location` to animated-route-switch */}
            <animated-route-switch location={this.location}>
              {/* <animate-presence> should exist somewhere within these components */}
              <stencil-route url="/" exact={true} component="app-home" />
              <stencil-route url="/profile/:name" component="app-profile" />
            </animated-route-switch>
          </stencil-router>
        </main>
      </div>
    );
  }
}
injectHistory(AppRoot);
```

3. Apply your animations on `[data-enter]` and `[data-exit]` as usual.

> As noted above, if your Stencil components rely on `shadow` encapsulation, Animate Presence won't work as expected (yet). I'm working on it.
> For now, you can either use `scoped` encapsulation or stay tuned for updates!

---
_Source: https://npm.io/package/animate-presence · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
