Index of workJob 007 of 030
007Open source2026

reveal-on-scroll

Open-source npm package: a tiny, typed alternative to AOS

An open-source library on npm that reveals elements as you scroll: the animations and options of AOS (Animate On Scroll) in half the size, typed, with no dependencies, and accessible by default.

  • TypeScript
  • CSS
  • Vite
  • Vitest
  • happy-dom
  • Playwright
  • GitHub Actions
  • GitHub Pages
  • npm
  • Scroll-driven animations
  • MutationObserver
  • ResizeObserver
Job ticketNo.007
Built at
Open source
Sector
Developer tools
Period
2026
Role
Author and maintainer — API design, code, tests, documentation and releases
Team
A solo project
Operations
  • Web platform
  • Motion
Checked
Live
ABrief

reveal-on-scroll animates elements in as they reach the screen, the way AOS (Animate On Scroll) does, with the same animations and options. Switching from AOS is a find-and-replace: data-aos becomes data-ros, and AOS.init() becomes ROS.init().

AOS is still downloaded about 174,000 times a week, but it hasn’t had a release since 2018, and its long-standing problems remain: triggers drift when images, fonts or new content load (so pages call refresh() by hand), it has no reduced-motion support, and content can stay hidden when JavaScript is off.

I wrote reveal-on-scroll to fix those problems while keeping everything AOS users rely on: a small, typed library with no dependencies, published on npm with ESM and UMD builds, a live demo and automated releases.

BWhat I did
  1. Op 01

    Rebuilt AOS’s full set of 27 animations (fade, zoom, flip and slide), its 20 easings and its options, covering both AOS 2.3.4 and the 3.0 beta (mirror, class names, events), under data-ros names.

  2. Op 02

    Kept triggers accurate as the page changes: positions are re-read once per animation frame while scrolling, and a ResizeObserver and a MutationObserver pick up loaded images, fonts and added or changed elements, so there is no refresh() to call.

  3. Op 03

    Made it accessible by default: with “reduce motion” on, every animation becomes a simple fade; start states only apply while scripting is on, so the page stays readable with JavaScript off; and if the script never starts, hidden elements appear after 3 seconds.

  4. Op 04

    Added an optional scroll-scrubbed mode (data-ros-scrub) on the browser’s native scroll-driven animations, so no JavaScript runs while you scroll; browsers without them fall back to the normal animation.

  5. Op 05

    Fixed AOS’s rough edges: any duration, delay or CSS easing function instead of 50 ms steps, the real cubic and quart curves, animations that end at transform: none so fixed-position children keep working, and events dispatched on the element itself.

  6. Op 06

    Shipped ESM and UMD builds with TypeScript types and no dependencies: safe to import during server-side rendering, or loaded from a CDN with a plain script tag.

  7. Op 07

    Covered it with 37 unit tests (Vitest with happy-dom) and 11 browser tests (Playwright); CI runs the type check, the unit tests and the browser tests in Chromium, Firefox and WebKit on every push and pull request.

  8. Op 08

    Automated the releases: publishing a GitHub release publishes to npm through trusted publishing (OIDC, no stored token) once the type check, tests and build pass, and the demo page deploys to GitHub Pages on every push.

COutcome
  • Published on npm as reveal-on-scroll (MIT licence), with a live demo on GitHub Pages and a changelog for every release.
  • All of AOS’s animations and options in 3.6 kB gzipped, about half of AOS’s 6.7 kB.
  • Tested in Chromium, Firefox and WebKit on every push, and released from GitHub without a stored npm token.
  1. 3.6 kBgzipped, JavaScript and CSSAOS 2.3.4 is 6.7 kB
  2. 27animationsfade, zoom, flip and slide: the full AOS set
  3. 48automated tests37 unit tests and 11 browser tests, in three browser engines
  4. 0dependenciesESM and UMD builds with TypeScript types
DSchematicHow it fits together

Under the hood

Your pagedata-ros attributesStylesheethides only if JS runsROS.init()options, attributesObserversresizes, new elementsScrub modenative scroll timelineOnce a framepositions re-readros-animateclass + ros:in events
  1. Your page (data-ros attributes)Stylesheet (hides only if JS runs)
  2. Your page (data-ros attributes)ROS.init() (options, attributes)
  3. Stylesheet (hides only if JS runs)Scrub mode (native scroll timeline)
  4. ROS.init() (options, attributes)Observers (resizes, new elements)
  5. ROS.init() (options, attributes)Once a frame (positions re-read)
  6. Observers (resizes, new elements)Once a frame (positions re-read)
  7. Once a frame (positions re-read)ros-animate (class + ros:in events)
The stylesheet hides elements only while scripting is on; the script re-reads positions once a frame and adds ros-animate; scrub mode hands the animation to the browser. With reduced motion, every animation becomes a fade.
EPrintsLive screens

From the live site

The reveal-on-scroll demo page: “Reveal on scroll.”, with the npm install command
Print 01The reveal-on-scroll demo page: “Reveal on scroll.”, with the npm install command
The demo’s Fade and Zoom sections: every animation as a tile, revealed as you scroll
Print 02The demo’s Fade and Zoom sections: every animation as a tile, revealed as you scroll
The demo’s Options and Scrubbed sections: delays, durations and easings, and cards that follow the scroll
Print 03The demo’s Options and Scrubbed sections: delays, durations and easings, and cards that follow the scroll
The reveal-on-scroll demo on a phone
Print 04The reveal-on-scroll demo on a phone
Next job008UAE Textile Manufacturer PlatformScroll-driven storytelling and an Umbraco corporate site