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

tween-fn

v1.1.7

Published

A lightweight animation library which focuses on providing an expressive functional API to compose complex animations without assuming anything about your application

Readme

Tween-fn

A tiny (~1.7kb gzipped) library for coordinating complex animation sequences, it focuses on providing the means for writing a description of a sequence using a small but powerful set of function primitives which are fully composable. Unlike existing alternatives like GSAP or animejs, tween-fn does not assume anything about your application, consequently, it doesn't know anything about the DOM or whichever JS framework you happen to prefer, this allows you to be very explicit about what you're trying to achieve

What do we mean by complex animation sequences?

It basically boils down to two concepts: parallel and sequencial execution, and some of the most popular libraries available today do offer some degree of support for these concepts, but they usually delegate the task of composition to the end-user in one way or another

Libraries like react-motion although great in their own right, seem to be optimized for one-off animations (what we call units), the API for composing sequences is weird and seems to have been included retroactively

This need can arise surprisingly often, here's an example from a simple trivia app:

[GIF]

Notice how we needed the background to begin a looping animation right away, then the logo fades in followed by a tap icon a few milliseconds later, which also includes a looping animation to emphasize the tapping gesture. This can not only happen on macro interactions like a page transition, but also on micro interactions like a button click:

[GIF]

Here we wanted to [...], you might call both separately with a delay, but its nice to have the idea of this being a single unit be captured in the code. You could argue that the difference between orchestrating the two separately and controlling both transitions in a single unit is barely noticeable, and you'd be right, but still, we'd like to have a choice to decide wether or not it makes sense to make that consession and not have the limitations of a library to make the decision for us

Enabling composition

One of the main pain points with existing solutions is how hard it is to organize a sequence of animations, we see this as being fundamentally an issue of expressiveness, and as it turns out, you can address most of these problems by making your primitives composable

// `anime.js`'s Timeline function forces you to
// "flatten" the sequence


// composable primitives allow you to group
// sequences which are semantically related
// in a natural way

Promoting abstraction

By abstracting out highly composable pieces into reusable units, you gain a good deal of readability as the intention behind the sequence is embeded into the code itself:

const master = parallel([
  backgroundShapesLoop,
  sequence([
    bannerIntro,
    parallel([
      tapIndicatorIntro,
      tapIndicatorLoop,
    ]),
  ]),
]);

This is the actual code for that intro sequence we showed earlier, the main takeaway from this snippet of code is that we've rescued the intention behind the animation and made it explicit. Taking advantage of these abstraction and composition abilities leads to code that is easy to reason about

Playground

Here's a remake of a button taken from Super Mario Party, unsurprisingly, there's a lot that goes into making those juicy Nintendo animations, check the code to see what's under the hood, or read this article which goes in-depth into the making of this button

Closing thoughts

The web as we know it is constantly changing, and as the pool of devices that access our webapps become more and more capable, new possibilites are unlocked. Check out the react integration, for bugfixes and suggestions you can use the appropiate channels on Github.

react integration

There are X things we want from a React integration [...]

redux integration using a queue

Useful when you need to split a sequence in different places. This rescues the intention and makes it explicit

Installation

yarn add tween-fn

or

npm i -S tween-fn

Quickstart

We start by writing a sequence using the primitives provided by the library

const seq = sequence([
  unit({
    duration: 250,
    change: (value) => {
      el.style.width = `${interpolate(value, 100, 150)}px`;
    },
  }),
  unit({
    duration: 500,
    ease: easings.SQUARED,
    change: (value) => {
      el.style.transform = `translateX(${interpolate(value, 0, 50)})`;
    },
  }),
]);

And then pass that to run to play it

run(seq);

For cancelation, you can use the subscription object returned from run

const subscription = run(seq);

// somewhere else in your application...
subscription.unsubscribe();

Recipes

Animating multiple transforms

To apply multiple transforms at the same time, use the meta object supplied to your callback functions to rescue the original value of the transform, then compute the new transformation using the computeTransform function

unit({
  begin: (meta) => { meta.originalTransform = circle.style.transform; },
  change: (value, { originalTransform }) => {
    circle.style.transform = computeTransform(
      originalTransform,
      `scale(${interpolate(value, 1, 1.2)}) translateX(${interpolate(value, 0, 100)}px)`,
    );
  },
});

SVG path animations

Use the interpolatePath utility to easily animate between two different paths

const path1 = '';
const path2 = '';

unit({
  change: (value) => {
    path.setAttribute('d', interpolatePath(value, path1, path2));
  },
});

Staggering

Given a list of items, you can coordinate a staggering animation using mergeAll and adding delay to each animation

mergeAll(nodeList.map((node, i) => unit({
  delay: 100 * i, // 100 miliseconds of delay between each animation
  change: (value) => {
    // do something...
  },
})));

API

unit

Used to create an animation, takes the following options

interface TweenOptions {
  iterations?: number;
  direction?: directions;
  from?: number;
  to?: number;
  delay?: number;
  duration?: number;
  ease?: easingFn;
  begin?: (meta?: object | null) => void;
  update?: (y: number, meta?: object | null) => void;
  complete?: (y: number, meta?: object | null) => void;
  change?: (y?: number, meta?: object) => void;
  loop?: (y?: number, meta?: object) => void;
  meta?: object;
}

mergeAll

Used for parallel execution of multiple animations

mergeAll(ts: Array<Tween>): Tween

sequence

Describes a sequence of animations, where each animation supplied will run only after the previous one has completed (unless a negative value for delay is used)

sequence(ts: Array<Tween>): Tween

run

Executes the given description

run(tween: Tween): Subscription

Utils

easings

A dictionary holding common easing functions, available functions are

easings.LINEAR
easings.SQUARED
easings.CUBIC
easings.QUART
easings.QUINT
easings.EASE_OUT_QUINT
easings.EASE_IN_OUT_QUINT
easings.EASE_OUT_ELASTIC

Check out easings.net for more information regarding these

interpolate

Linearly interpolates between two values

interpolate(progress: number, start: number, end: number): number

interpolatePath

Linearly interpolates between two paths, paths must have the same number of points

interpolatePath(progress: number, p1: string, p2: string): string

computeTransform

Replaces values defined by source from target. Returns the new transform string

computeTransform(target: string, source: string): string

// outputs `translate(-50%, -50%) scale(1) rotate(0)`
computeTransform(
  'translate(-50%, -50%) scale(1.2) rotate(5deg)',
  'scale(1) rotate(0)'
)

Roadmap

  • playback controls
  • more easing functions
  • add examples