# An AOS alternative for Astro

> AOS keeps animated elements invisible until its script runs, shows elements without animating them after a ClientRouter navigation, and has had no release since October 2018. Reveal does the same job without those problems.

## How AOS behaves in an Astro project

AOS (Animate On Scroll) is a common choice for scroll reveals, with about
220,000 npm downloads a week in September 2026. Three of its behaviours matter
on an Astro site. The measurements below use AOS 2.3.4, its current release,
on Astro 7.3.2 in Chromium, Firefox and WebKit.

- **Animated elements stay invisible until the script runs.** AOS's stylesheet
  gives every `fade` and `zoom` element `opacity: 0`, and only the script
  reveals it. With JavaScript turned off, or with the AOS script blocked, a
  `data-aos="fade-up"` heading stayed at `opacity: 0`. This was reported
  in 2016 ([michalsnik/aos#81](https://github.com/michalsnik/aos/issues/81)).
- **After a `<ClientRouter />` navigation, elements appear without
  animating.** AOS still finds the new elements, because it watches the DOM
  for them. But `AOS.init()` writes the default duration, easing and delay as
  `data-aos-*` attributes on `<body>`, and the router swaps in a new `<body>`
  without them. The `transition-duration` was `0.4s` on the first page and
  `0s` on every page after it. Calling `AOS.init()` again on
  `astro:page-load` brings the duration back and adds another `scroll` and
  `resize` listener to `window` on each navigation: four of each after three
  navigations. Sites without the router are not affected.
- **No release since October 2018.** Versions 2.3.4 and 3.0.0-beta.6 were
  both published on 3 October 2018
  ([npm](https://www.npmjs.com/package/aos?activeTab=versions)), and the
  default branch has no commit after that day
  ([history](https://github.com/michalsnik/aos/commits/next/)). AOS has no
  reduced-motion handling of its own, and an issue asking the README to show
  how to turn it off for `prefers-reduced-motion` has been open since August
  2020 ([michalsnik/aos#611](https://github.com/michalsnik/aos/issues/611)).

## Reveal, the replacement

```astro
---
import Reveal from '@moonarc/core/Reveal';
---
<Reveal><h2>Appears once, stays.</h2></Reveal>
<Reveal from="left" distance={40} delay={80}><p>Direction, distance, delay.</p></Reveal>
<Reveal as="ul" stagger={60}>
  <li>one</li><li>two</li><li>three</li>
</Reveal>
<Reveal scrub><p>Progress follows the scroll position.</p></Reveal>
```

- The server HTML is the end state. Content is hidden only behind
  `html[data-ma-js]`, which the integration sets just before first paint.
  Without JavaScript the page stays visible.
- It binds through the shared runtime: every instance is observed after every
  navigation, observers are disconnected before the swap, and each element is
  bound exactly once, however often the router's events fire.
- Reduced motion keeps a fast fade and drops the travel.
- `scrub` uses `animation-timeline: view()` where the browser has it and an
  observer-driven fallback elsewhere, so it works everywhere.

Cost, measured on [/compare/](/compare/) with one reveal per page, gzip on both
sides: a page with one Reveal loads 1.2 kB gzip of JavaScript and 1.7 kB gzip of
CSS, and a page with AOS 2.3.4 loads 4.8 kB gzip of JavaScript and 2.2 kB gzip of
CSS.

## Migration table

| AOS | Reveal |
|---|---|
| `data-aos="fade-up"` | `<Reveal>` (default) |
| `data-aos="fade-left"` | `from="right"` |
| `data-aos="zoom-in"` | `scale={0.96}` |
| `data-aos-delay="100"` | `delay={100}` |
| `data-aos-duration="600"` | `duration={600}` |
| `data-aos-once="false"` | `once={false}` |
| `data-aos-offset="120"` | `rootMargin="0px 0px -120px 0px"` |
| `data-aos-anchor-placement` | `threshold={0.2}` |

See the [Reveal](/components/reveal/) page for every prop and the measured cost.

[Animation libraries for Astro, measured](/compare/) builds the same scroll reveal with AOS, GSAP, Motion,
AstroAnimate, astro-reveal and Reveal, and records the JavaScript each loads and what each does after a
`ClientRouter` navigation, in Chromium, Firefox and WebKit.