npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rcaferati/wac

v1.2.0

Published

Tiny JavaScript utility for CSS transitionend/animationend callbacks and frame/layout timing helpers.

Readme

WAC — CSS transition and animation controls

NPM

Tiny JavaScript utility for CSS transitionend / animationend callbacks and frame/layout timing helpers.

WAC provides small Promise-based helpers to:

  • wait for a transition or animation to finish
  • ignore bubbled events from child elements
  • schedule code on the next layout / future frames
  • optionally use refresh-rate independent timing (fixedFrameThrower)

Installation

npm

npm install @rcaferati/wac

yarn

yarn add @rcaferati/wac


Quick usage

Wait for a transition to finish

import { onceTransitionEnd } from "@rcaferati/wac";

const box = document.querySelector(".box");

box.classList.add("animated");
box.classList.add("move");

onceTransitionEnd(box).then((event) => {
  // transition finished on `box`
});

Wait for a specific transitioned property

import { onceTransitionEnd } from "@rcaferati/wac";

onceTransitionEnd(box, { propertyName: "transform" }).then(() => {
  // resolves only when the transform transition ends
});

Wait for an animation to finish

import { onceAnimationEnd } from "@rcaferati/wac";

onceAnimationEnd(box).then((event) => {
  // animation finished on `box`
});

Run code after the next CSS layout

import { beforeCssLayout } from "@rcaferati/wac";

beforeCssLayout(() => {
  // runs on next animation frame (or immediately if rAF is unavailable)
});

Wait N real display frames (rAF hops)

import { frameThrower } from "@rcaferati/wac";

await frameThrower(2); // waits using real display frames

Wait N virtual frames (refresh-rate independent)

import { fixedFrameThrower } from "@rcaferati/wac";

// Interprets "10 frames" at 60fps (~166.7ms),
// even on 120Hz / 144Hz displays.
await fixedFrameThrower(10);

Basic HTML/CSS example

<style>
  .box {
    width: 40px;
    height: 40px;
    background: #222;
  }

  .animated {
    transition: transform 0.4s linear;
  }

  .move {
    transform: translateX(100px);
  }
</style>

<div class="box"></div>

<script type="module">
  import { onceTransitionEnd } from "@rcaferati/wac";

  const box = document.querySelector(".box");

  box.classList.add("animated");
  box.classList.add("move");

  onceTransitionEnd(box, { propertyName: "transform" }).then(() => {
    console.log("Done");
  });
</script>

API

onceTransitionEnd(element, options?)

Waits for the next matching transitionend event on element.

Returns:

  • Promise<Event | false>

Options

  • tolerance?: number
    • Number of matching events to skip before resolving.
    • 0 resolves on the first matching event.
  • propertyName?: string
    • Preferred transition property filter (example: "transform").
  • property?: string
    • Backward-compatible alias for propertyName.

Example:

await onceTransitionEnd(el, { propertyName: "opacity" });

onceAnimationEnd(element, options?)

Waits for the next matching animationend event on element.

Returns:

  • Promise<Event | false>

Options

  • tolerance?: number
    • Number of matching events to skip before resolving.
  • animationName?: string
    • Optional animation name filter.

Example:

await onceAnimationEnd(el, { animationName: "fade-in" });

setCssEndEvent(element, type, options?)

Low-level helper used internally by onceTransitionEnd / onceAnimationEnd.

  • type: "transition" or "animation"
  • Returns Promise<Event | false>

Notes:

  • Ignores bubbled child events (only resolves for the exact target element).
  • Preserves historical "latest call wins" behavior per element (a new pending listener replaces a previous one created by WAC on the same element).

beforeCssLayout(callback)

Runs callback on the next animation frame.

Fallback behavior:

  • If requestAnimationFrame is unavailable, runs immediately.

beforeFutureCssLayout(frames, callback)

Runs callback a number of frames into the future, plus one extra frame.

Semantics:

  • frames = 0 -> next animation frame
  • frames = 1 -> roughly two frame hops from now

Fallback behavior:

  • If requestAnimationFrame is unavailable, runs immediately.

recursiveAnimationFrame(frames, callback)

Low-level helper that recursively waits for frames animation frames and then runs callback.

This powers the layout/frame helpers and can be used directly if needed.


frameThrower(frames)

Promise wrapper around beforeFutureCssLayout.

Uses real display frames (requestAnimationFrame hops), so elapsed time depends on monitor refresh rate (60Hz / 120Hz / etc).

Returns:

  • Promise<void>

fixedFrameThrower(frames, fps = 60)

Refresh-rate independent timing helper.

Same intent as frameThrower(), but frames are interpreted as "virtual frames" at fps (60 by default), so timing is stable across 60Hz/120Hz/etc displays.

It still waits at least 1 requestAnimationFrame first, preserving CSS/layout timing semantics similar to beforeFutureCssLayout.

Examples (fps = 60):

  • fixedFrameThrower(0) -> ~1 rAF (next layout)
  • fixedFrameThrower(10) -> ~1 rAF + ~166.7ms

Returns:

  • Promise<void>

onceNextCssLayout()

Resolves after two animation frames (double-rAF).

This is useful when you want DOM updates and style calculations to progress enough for reliable transition triggering or measurement.

Returns:

  • Promise<void>

framesToMs(frames, fps = 60)

Converts "virtual frames" to milliseconds.

Examples:

  • framesToMs(1) -> ~16.67
  • framesToMs(10) -> ~166.67
  • framesToMs(10, 120) -> ~83.33

Returns:

  • number

Notes and caveats

  • CSS end events may not fire if:
    • duration/delay is 0
    • the property/animation does not actually run
    • the animation/transition is canceled or interrupted
    • the element is removed before completion
  • If no supported CSS end-event name can be resolved for the element, WAC resolves with false.
  • In non-browser environments (or when requestAnimationFrame is unavailable), frame/layout helpers fall back to immediate execution.

Browser support

WAC targets modern browsers and includes small legacy event-name fallbacks (WebKit / old Opera) for compatibility.


Author

Rafael Caferati
Website: caferati.dev
LinkedIn: linkedin.com/rcaferati
Instagram: instagram.com/rcaferati


License

MIT License

Copyright (c) 2018 @rcaferati