Resources / Guide · 2026-09-12
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
fadeandzoomelementopacity: 0, and only the script reveals it. With JavaScript turned off, or with the AOS script blocked, adata-aos="fade-up"heading stayed atopacity: 0. This was reported in 2016 (michalsnik/aos#81). - After a
<ClientRouter />navigation, elements appear without animating. AOS still finds the new elements, because it watches the DOM for them. ButAOS.init()writes the default duration, easing and delay asdata-aos-*attributes on<body>, and the router swaps in a new<body>without them. Thetransition-durationwas0.4son the first page and0son every page after it. CallingAOS.init()again onastro:page-loadbrings the duration back and adds anotherscrollandresizelistener towindowon 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), and the
default branch has no commit after that day
(history). 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-motionhas been open since August 2020 (michalsnik/aos#611).
Reveal, the replacement
---
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.
scrubusesanimation-timeline: view()where the browser has it and an observer-driven fallback elsewhere, so it works everywhere.
Cost, measured on /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 page for every prop and the measured cost.
Animation libraries for Astro, measured 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.