# Core

> Add scroll reveal animations to plain JavaScript with @awesome-reveal/core: reveal(), observe(), play() and hiddenStyle, or build an adapter for a framework.

`@awesome-reveal/core` is the framework-independent part of awesome-reveal. The framework adapters use it. If your app uses React, Vue or Astro, install the package for your framework instead. See [Getting started](/getting-started/).

Use this package with plain DOM code, or to write an adapter for another framework.

## Install

```sh
pnpm add @awesome-reveal/core
```

## Reveal an element

```ts
import { reveal } from "@awesome-reveal/core";
import { slide } from "@awesome-reveal/core/keyframes";

const controller = reveal(document.querySelector<HTMLElement>(".hero")!, {
  keyframes: slide({ direction: "left" }),
  duration: 800,
});

// Later, for example when the element leaves the page:
controller.destroy();
```

The options are the same as in the adapters. See [Options](/concepts/options/).

The package is ESM-only. Load it with a bundler, such as Vite, or in a `<script type="module">`. To use the bare `@awesome-reveal/core` specifiers there, add an import map.

## Avoid a flash on page load

`reveal` hides the element only when the script runs. Until then, the browser shows the HTML, so an element can show, disappear and then animate in. To hide it from the first paint, put the hidden state in the HTML. The reveal removes the `opacity: 0` and the `data-reveal` attribute:

```html
<section class="card" style="opacity: 0" data-reveal="hidden">…</section>
```

* Put the hidden state only on elements that your script reveals. An element that no `reveal` call finds stays hidden.
* Do not hide the content at the top of the page. It then waits for the script, which can delay the Largest Contentful Paint.
* If JavaScript does not run, the content stays hidden. To show it, add the [`<noscript>` style](/concepts/accessibility/#pages-without-javascript).

Under a strict Content Security Policy, the inline `opacity: 0` style needs `style-src-attr 'unsafe-inline'`, and the `<noscript>` style needs a nonce or a hash. To avoid both, put only `data-reveal="hidden"` in the HTML, and add this rule to your stylesheet:

```css
@media (scripting: enabled) {
  [data-reveal="hidden"] {
    opacity: 0;
  }
}
```

* The rule applies only when JavaScript runs, so the page needs no `<noscript>` style.
* `reveal` sets its own styles through `element.style`, which the policy does not block.
* The `scripting` media feature needs Chrome 120, Firefox 113 or Safari 17. In older browsers, the element shows until the script hides it.

## Entry points

| Import path                      | Contents                                                       |
| -------------------------------- | -------------------------------------------------------------- |
| `@awesome-reveal/core`           | `reveal`, `observe`, `play`, `hiddenStyle` and `createReveal`. |
| `@awesome-reveal/core/keyframes` | The built-in keyframes, `defineKeyframes`, and their types.    |

## API

### `reveal(element, options?)`

Hides the element, and plays the keyframes when the element enters the viewport. While the element is hidden, it has an inline `opacity: 0` and `data-reveal="hidden"`. The reveal removes `data-reveal` and restores the inline `opacity` that the element had. It removes an `opacity: 0` that comes from `data-reveal="hidden"` markup.

`reveal` returns a controller:

* `revealed`: `true` while the element is revealed. With `once: false`, it goes back to `false` when the element leaves the viewport.
* `setOptions(options)`: replaces the options. A change of `when` applies at once. A change of `threshold`, `rootMargin` or `once` restarts the observer. The other options apply at the next reveal.
* `destroy()`: stops the observation, cancels the animation and restores the element, so it shows. After `destroy()`, `setOptions()` does nothing.

### `observe(element, options, onChange)`

Calls `onChange(true)` when the element enters the viewport. When `once` is `false`, it also calls `onChange(false)` when the element leaves the viewport. It uses `threshold`, `rootMargin` and `once`, and returns a function that stops the observation.

If the browser rejects an option, such as an invalid `rootMargin`, `observe` calls `onChange(true)`, so the element shows. Without `IntersectionObserver`, it does the same at once. The error is reported once for each message, from a microtask: it reaches `window.onerror` and does not throw in your code. `reveal` then stays shown. With the default `once`, a corrected option does not hide it again.

### `play(element, options?)`

Plays the keyframes on the element, or on its children with `stagger`. It uses `keyframes`, `duration`, `delay`, `stagger` and `reducedMotion`, and returns a function that cancels the animation.

Call `play` in the same frame that removes the hidden style. If you do not, the element shows at its resting style for one frame.

### `hiddenStyle`

The style that hides an element until the reveal: `{ opacity: "0" }`. A server renderer applies it to the markup, so that the element does not show before the client code runs.

## TypeScript

`@awesome-reveal/core` exports the types of its API:

* `RevealOptions` and `RevealController` for `reveal`.
* `RevealState` for `createReveal`.
* `ObserveOptions` for `observe`, and `PlayOptions` for `play`.

`@awesome-reveal/core/keyframes` exports `RevealKeyframe`, `KeyframesFactory`, `KeyframesDefinition`, `SpringOptions`, and the options type of each factory, such as `SlideOptions`.

## Write an adapter

Use `reveal` when your code owns the element. When your framework renders the style of the element, use `createReveal`. It decides when the element is revealed and plays the animation, but it does not change the DOM.

1. Call `createReveal(options)` once for each element.
2. Render the element with `hiddenStyle` and `data-reveal="hidden"`. Read `revealed` for the first render: it is `true` before the first `attach` only when `when` is `true`.
3. Call `subscribe(listener)`. The listener hides or shows the element before the next frame. There is one listener, and a new call replaces it.
4. Call `attach(element)` when the element mounts. The state observes the element and plays the keyframes. A new element keeps the state and does not play again. `attach(null)` stops the observation.
5. Call `setOptions(options)` when the options change.

`observe` and `play` are the lower-level pieces of `createReveal`. Use them when you need only one of them.

The React adapter follows these steps: [`useReveal.ts`](https://github.com/awesome-reveal/awesome-reveal/blob/next/packages/react/src/useReveal.ts) builds on `createReveal`, and its `getProps` sets `data-reveal`.
