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

baffle-ts

v1.0.0

Published

A tiny dependency-free TypeScript library for obfuscating and revealing DOM text. A modern TypeScript successor to the archived baffle.js.

Readme

TypeScript GitHub contributors GitHub License GitHub Actions Workflow Status GitHub package.json version Bundle Size

ESLint Prettier Vitest Maintenance Socket Badge

NPM Downloads


baffle-ts targets DOM text, obfuscates it with configurable characters, and reveals it over time. It is dependency-free, framework-agnostic, typed, and ships ESM, CommonJS, UMD, minified UMD, and TypeScript declarations.

import { baffle } from 'baffle-ts';

const title = baffle('.headline', {
  characters: 'abcdefghijklmnopqrstuvwxyz0123456789',
  speed: 45,
});

title.start();
await title.reveal(900);
  • Dependency-free runtime - no framework or utility dependencies.
  • Tiny bundle - Bundlewatch checks every JavaScript output.
  • Framework-agnostic - pass a selector, Element, NodeList, or any element collection.
  • Typed API - TypeScript declarations are included.
  • Multiple builds - ESM, CommonJS, UMD, and minified UMD.

Credit

baffle-ts is a modern TypeScript rewrite inspired by baffle.js by Cam Wiegert, which has been archived since 2020. The original is MIT licensed and its copyright notice is retained in LICENSE.

This is a from-scratch implementation, not a fork. It keeps the shape of baffle's API because that API was good, while modernising the internals and the build. See Differences from baffle.js before migrating.

Getting Started

Installation

npm install baffle-ts

Live demo

The demo is hosted on GitHub Pages:

andreasnicolaou.github.io/baffle-ts

You can also serve docs/ with any static file server and open docs/index.html. The demo loads baffle-ts from the CDN, so no local build is required.

Browser Usage

<h1 data-headline>baffle-ts</h1>

<script src="https://unpkg.com/baffle-ts@latest/dist/index.umd.min.js"></script>
<script>
  const title = Baffle.baffle('[data-headline]');
  title.start();
  title.reveal(900);
</script>

API

const instance = baffle(target, options);

target can be a CSS selector, a single Element, or an iterable/array-like collection of elements.

baffle('.headline');
baffle(document.querySelector('.headline')!);
baffle(document.querySelectorAll('.headline'));

baffle() is the convenience factory and returns a Baffle instance. You can also construct the class directly when that better fits your code:

import { Baffle } from 'baffle-ts';

const instance = new Baffle('.headline', { speed: 45 });

Framework integration

baffle-ts works with React, Vue, Angular, Svelte, and other browser frameworks because it operates on real DOM elements rather than framework-specific components. Create an instance only after the element has mounted, and call destroy() when the owning component unmounts.

For server-side rendering, initialize baffle-ts on the client. Selector targets require document; passing an existing Element is safe in environments where a DOM element is available.

The library updates textContent directly, so let it own the animated element's text while an effect is active. Avoid rendering competing text into that same element from the framework until the effect has finished or been destroyed.

Options

  • characters: string or array of characters used while text is obfuscated.
  • exclude: string or array of characters that should not be replaced.
  • speed: minimum milliseconds between obfuscation frames.
  • random: custom random number function, useful for deterministic tests.
  • respectReducedMotion: honour prefers-reduced-motion. Defaults to true.

Instance Methods

  • start(): repeatedly obfuscates unrevealed text.
  • stop(): stops active animation without revealing.
  • once(): performs one obfuscation frame.
  • reveal(duration?, delay?): reveals text over time and resolves with the instance when the reveal finishes or is stopped.
  • set(options): updates animation options, including mid-animation.
  • text(valueOrResolver): replaces the managed text.
  • refresh(): reads the current DOM text back into the instance.
  • destroy(): stops animation and restores the managed text.

Waiting for a reveal

reveal() returns a real promise, so code can wait for the reveal to settle:

// Configure with the usual chain, then reveal
const title = baffle('.headline')
  .start()
  .set({ speed: 100 })
  .text(() => 'Hi dad!');

// Await completion before starting the next action
await title.reveal(900);
showTheNextThing();

// It is a genuine promise, so it composes
await Promise.all([title.reveal(400), subtitle.reveal(600)]);

Calling stop(), start(), or a new reveal interrupts an active reveal() call and resolves its promise with the instance.

Accessibility

When the user has prefers-reduced-motion: reduce set, baffle-ts skips obfuscation entirely and leaves text readable: start() and once() render the real text, and reveal() restores it and resolves immediately. Scrambled-but-static text would be less readable than the animation it replaces, so nothing is obfuscated at all.

Opt out with respectReducedMotion: false. Outside a browser, or where matchMedia is unavailable, animation runs normally.

Rendering

The animation loop is driven by requestAnimationFrame, so it stays in step with the display, avoids interval drift, and pauses automatically in background tabs. speed throttles how often characters churn. Where frames are unavailable, it falls back to setTimeout.

Differences from baffle.js

All six baffle.js methods keep their names and argument shapes, so most code moves across unchanged. The remaining differences are defaults and additions:

| | baffle.js 0.3.6 | baffle-ts | | --------------------------- | ----------------------------------- | -------------------------------- | | reveal() default duration | 0 | 600 | | reveal() return value | instance | Promise<Baffle> | | Default characters | Aa…Zz~!@#$%^&*()-+=[]{}\|;:,./<>? | A–Z a–z 0–9 #%&*+-=?@ | | Default exclude | [' '] | ' \t\n\r' | | exclude semantics | replaces the default | appends to the default | | text() argument | function only | string or function | | text() resolver signature | (currentText) | (currentText, element, index) | | Animation loop | setInterval | requestAnimationFrame | | Reduced motion | not handled | obfuscation skipped by default | | Extra methods | — | refresh(), destroy() | | Extra options | — | random, respectReducedMotion |

Migrating from baffle.js

Most method calls move across unchanged. reveal() now returns a promise, so retain the instance when later code needs to configure or stop it. The other changes that alter behaviour are the defaults:

// Restore baffle.js's character set and instant reveal
const b = baffle('.headline', {
  characters: 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz~!@#$%^&*()-+=[]{}|;:,./<>?',
});

await b.reveal(0);

Whitespace beyond the space character (\t, \n, \r) is excluded by default, and anything you pass to exclude is added to that set rather than replacing it.

Users with prefers-reduced-motion: reduce will see no obfuscation at all. If you need the previous unconditional behaviour, pass respectReducedMotion: false.

Development

npm install
npm run check
npm run test:coverage

The check script runs ESLint, Prettier validation, Vitest, the Rollup build, and Bundlewatch. test:coverage runs the same test suite with V8 coverage enabled and enforces 100% statements, lines, and functions, plus at least 95% branch coverage.

License

baffle-ts is licensed under the MIT License (c) Andreas Nicolaou, incorporating the MIT-licensed copyright notice of baffle.js (c) Cam Wiegert.