# Shifty: a low-level TypeScript tweening engine

> Shifty interpolates numeric state over time and gives application authors control over rendering, playback, and lifecycle hooks. It suits developers building custom animation tools, but it deliberately leaves scene composition and higher-level scheduling outside its scope.

**jeremyckahn/shifty** — The fastest TypeScript animation engine on the web

- Repository: https://github.com/jeremyckahn/shifty
- Website: https://jeremyckahn.github.io/shifty/doc/
- Stars: 1,553 · Forks: 84
- Language: TypeScript
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/jeremyckahn-shifty

## Shifty's tweening boundary and intended users

Shifty solves a narrow problem: changing a set of numeric values between defined states over a specified duration. The caller supplies the starting values, destination values, duration, easing name, and a render function. Shifty calculates the interpolated state and passes it back at scheduled update points. This makes the library a primitive for animation engines, drawing tools, state-transition systems, and custom interface components rather than a ready-made animation suite.

That boundary matters. Shifty does not describe itself as a component library, timeline editor, gesture recognizer, or scene graph. Its README positions it as code to be encapsulated by higher-level tools. Developers who only need one CSS transition may have less need for it, while developers implementing a reusable animation abstraction may find its small API useful.

The intended runtime is broader than the browser alone. The README states that Shifty officially supports evergreen browsers, Safari, and Node 10 and above, while Internet Explorer support is available through version 2. There is, however, a packaging detail that needs attention during adoption. The repository's `package.json` declares `"node": "22.18.0"`, which is narrower and newer than the README's general Node compatibility statement. A team using Node 22.18.0 has the clearest alignment with the current package metadata; the documentation does not explain the mismatch.

## How Shifty turns numeric state into rendered frames

A Shifty animation is configured through a tween object. The `from` and `to` properties define the numeric inputs, `duration` sets the animation length, and `easing` selects the interpolation behavior. The `render` callback receives the current state and applies it to the application. In the README example, that callback writes `x` and `scale` into a CSS transform string.

The important design choice is separation between state calculation and output. Shifty updates the tween state, but the caller decides whether that state changes an element style, a canvas drawing, an SVG attribute, or another target. This keeps rendering policy outside the engine. It also means that a missing or incorrect render callback will leave the numerical tween without a visible effect.

The initial `tween(...)` call is asynchronous and returns an object containing a `tweenable`. Subsequent calls on `tweenable.tween(...)` continue or transition the same animation state. Awaiting each call creates sequential behavior: the README example moves `x` to 200, returns it to 0, and then scales the element to 3. Shifty also supports individual playback control and lifecycle extension points, although the README does not enumerate every available hook.

The package exposes ES6, AMD, CommonJS, and UMD loading paths. TypeScript declarations are published through the `types` entry, and browser and Node builds point to different files in `package.json`. That split reflects the library's intent to work across browser animation and server-side JavaScript environments.

## Installing Shifty and running the first tween

The README gives one installation command. Run it in the directory containing the application that will consume Shifty. The package is added as a project dependency and can then be imported by its published name.

```bash
npm install --save shifty
```

For an ES module or TypeScript application, import the `tween` function. The following is the first useful example from the README. It expects an element with `id="tweenable"`; after the initial tween resolves, the same `tweenable` performs two awaited transitions.

```js
import { tween } from 'shifty'

const element = document.querySelector('#tweenable')
const { tweenable } = await tween({
  render: ({ scale, x }) => {
    element.style.transform = `translateX(${x}px) scale(${scale})`
  },
  easing: 'easeInOutQuad',
  duration: 500,
  from: { scale: 1, x: 0 },
  to: { x: 200 },
})
```

Continue the sequence by awaiting further tweens. The README's first transition moves only `x`; the next changes the scale.

```js
await tweenable.tween({
  to: { x: 0 },
})

await tweenable.tween({
  to: { scale: 3 },
})
```

This example assumes that the page contains the target element. The README does not provide markup for it, so document preparation remains the caller's responsibility. It also does not document cancellation, rollback, or a method for restoring a previous state.

## Promise sequencing, module formats, and test behavior

Promise support is one of Shifty's clearest integration points. The README uses an `async` function and awaits the initial tween, then awaits each follow-up tween. This makes sequencing explicit without requiring the caller to coordinate callbacks manually. The same approach can be applied to code that needs to run one transition before starting the next.

The package can be consumed through several module systems. An ES module uses a named `tween` import, while CommonJS destructures the same function from `require('shifty')`. AMD receives the module through its dependency array. The README also describes a UMD module, although the examples focus on application-level loading rather than browser script tags.

Jest requires one explicit operational adjustment in some versions. The README says Shifty may leave an open handle that prevents Jest from exiting. Its documented remedy is to call `shouldScheduleUpdate(false)` after all tests. This changes the scheduling behavior for the test process, so it should be confined to test setup rather than copied into production animation code.

```js
import { shouldScheduleUpdate } from 'shifty'

afterAll(() => {
  shouldScheduleUpdate(false)
})
```

The README does not explain which scheduler implementation is selected, how frame updates behave in each environment, or how a caller should drain pending updates manually. Those are areas that require direct inspection of the API documentation before relying on Shifty in headless or custom rendering environments.

## Shifty's limits for complete animation interfaces

The clearest limitation is the gap between a tween primitive and a complete animation system. Shifty supplies numerical interpolation and lifecycle control, but the README does not claim support for keyframe timelines, animation groups, scroll-linked motion, spring physics, gesture recognition, or declarative DOM bindings. Those features may be necessary for product animation work.

A second limitation concerns targets and units. The sample interpolates `x` and `scale`, then formats them into a CSS transform. Shifty does not automatically decide whether a number represents pixels, percentages, degrees, or another unit. The render callback must preserve the correct formatting. Adding or removing animated properties across successive tweens is also not explained in the README, so callers should define their state shape deliberately.

Shifty is not a substitute for CSS animation when the browser already provides the required behavior with less application logic. It becomes more relevant when several numeric values need coordinated interpolation, when animation state must participate in JavaScript control flow, or when a higher-level tool needs to encapsulate its own rendering layer.

Shifty instead offers a library-level tween state and a render callback, which can target non-DOM rendering systems such as canvas or SVG code.

## Shifty releases, compatibility, and upgrade boundaries

The repository is not archived, and the last push was on 2026-10-13. The listed releases are 3.0.6 from 2025-05-07, 3.0.5 from 2025-04-27, and 3.0.4 from 2025-03-05. These dates show recent repository activity, but they do not by themselves document a support policy for every runtime or browser version.

Upgrading from version 2 to version 3 carries API migration work. `Tweenable.formulas` became `Tweenable.easing`, `tweenConfig.step` was replaced by `tweenConfig.render`, and `tweenConfig.attachment` was replaced by `tweenConfig.data`. Several methods changed shape, including `set()` to `setState`, `get()` to the `state` getter, and `hasEnded()` to the `hasEnded` getter. Render handler parameters also changed order, with `timeElapsed` and `data` swapping positions relative to version 2. Version 2 remains on a separate branch with legacy documentation.

The MIT license permits use, modification, and redistribution according to its terms. That is relevant for embedding Shifty in a commercial or distributed tool, although the license file and applicable obligations should be reviewed for the intended distribution. The README does not document a SemVer support window, a formal deprecation period, or a migration command.

## Conclusion

Shifty fits developers who need a small tween primitive for a custom animation layer and can supply their own rendering and orchestration. It is a poor fit for applications expecting a complete UI animation system with timelines, gestures, or declarative components. Before adoption, verify that the package's Node 22.18.0 engine requirement is acceptable, confirm the required browser support, and check how `shouldScheduleUpdate(false)` affects test teardown.

## FAQ

### How do I install Shifty?

The README instructs users to run `npm install --save shifty`. The package can then be imported as `shifty`.

### How do I create a Shifty animation?

Pass `from`, `to`, `duration`, `easing`, and a `render` callback to `tween`. The callback receives the current interpolated state and can apply it to a DOM element or another rendering target.

### How can I run several Shifty tweens sequentially?

Await the initial `tween(...)` call, then call `await tweenable.tween(...)` for each following transition. The README demonstrates this pattern with movement followed by scaling.

### How do I prevent Shifty from keeping Jest open?

The README documents calling `shouldScheduleUpdate(false)` in test setup after all tests. This is intended to prevent Shifty's scheduled updates from keeping Jest from exiting.

### Can Shifty animate canvas or SVG content?

Shifty supplies interpolated values through its `render` callback, so the callback can update a custom drawing target. The README demonstrates DOM rendering through a CSS transform and does not provide a separate canvas or SVG API.

## Sources

- [jeremyckahn/shifty on GitHub](https://github.com/jeremyckahn/shifty)
- [License: MIT](https://github.com/jeremyckahn/shifty/blob/main/LICENSE)
- [Project website](https://jeremyckahn.github.io/shifty/doc/)
- [README](https://github.com/jeremyckahn/shifty/blob/main/README.md)
- [Releases](https://github.com/jeremyckahn/shifty/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/jeremyckahn-shifty
