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

spryte

v0.7.0

Published

Blazing fast 2D renderer for the web: 2-4x faster than the most popular one, at an eighth of its size. Verified in an open benchmark.

Readme

spryte

Blazing fast 2D rendering for the web: 2-4x faster than the most popular renderer, at an eighth of its size. Every number is checked pixel for pixel in 2dbench, an open benchmark you can run on your own device.

Most sprites each library holds at 60 fps, per 2dbench scene

You draw in immediate mode (sprites, bulk sprites, nine-slice panels, tiled fills, shapes and strokes, text, clips, masks, blend modes, filters, render targets and custom shaders), and spryte batches it onto WebGL2 with instancing, falling back to Canvas 2D automatically. No dependencies, no scene graph, no game framework.

Alpha. spryte is the renderer inside debuff, where games already run on it. The API may still change before 1.0. It's measured on desktop Chrome and Chromium (Windows and Linux; NVIDIA and AMD GPUs), iPhone Safari and Chrome, and Firefox on Android, and its one-call-per-sprite path is ahead of PixiJS on every device so far (results across devices). Safari on macOS isn't covered yet: run 2dbench on your device to help.

npm install spryte
import { createRenderer } from "spryte";

const draw = createRenderer(canvas, { smooth: false }); // smooth: false for pixel art
canvas.width = 640;
canvas.height = 360;
draw.resize(canvas.width, canvas.height);

function frame() {
  draw.begin("#111", 320, 180, 2); // clear colour, logical size, backing pixels per logical unit
  draw.rect(10, 10, 40, 20, "#cffb2d");
  draw.sprite(image, 160, 90, { rotation: 0.3, tint: "#f0f" });
  draw.text("hello", 8, 8);
  draw.end();
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

What it's for

Games are where spryte started, but anything that draws lots of moving 2D things in a browser fits:

  • Games: bullet hells, particle effects, crowds, tile maps, sprite-sheet animation.
  • Data visualisation: animated scatter plots, flow fields, live sensor and market data with thousands of points.
  • Creative coding and generative art: particle systems, trails and shader effects, without a framework.
  • Interactive sites and product UI: hero animations, confetti, backgrounds and effects that stay smooth on phones.
  • Simulations and education: agent-based models, physics demos and visual explanations.

Why spryte

Against PixiJS 8 on the same machine, assets and simulation, over 3-7 interleaved rounds in 2dbench, after a pixel-parity gate (desktop Chrome, RTX 5060 laptop, spryte 0.2.1). The comparison went through a fairness review aimed at breaking it. Method, raw data and caveats are in benchmarks.

| | spryte | PixiJS 8 | | | --- | --- | --- | --- | | JS at startup (brotli) | 11.3 kB | 87.8 kB | ~8x smaller | | 200k particles, per-particle scale, rotation, tint and alpha all changing | 5.8 ms | 24.2 ms (ParticleContainer) | 4.2x faster | | 50k sprites with rotation, scale and tint, one call each | 7.8 ms | 27.1 ms (Sprite) | 3.5x faster | | 200k sprites, position only, bulk | 2.8 ms | 7.2 ms (ParticleContainer) | 2.6x faster |

Frame time by sprite count for dynamic particles: spryte and PixiJS, with the 60 fps budget

Charts from 2dbench, the same run as the table (3-7 rounds depending on the scene). Lower lines are faster; the dashed line is the 60 fps budget.

In 2dbench's raycaster scene (a retro first-person maze drawn as thousands of texture slices), spryte held 20,000 enemies at 60 fps against PixiJS Sprite's 5,000. The gap is per-sprite work: a PixiJS Sprite is a scene-graph object updated, bounded and batched every frame, while spryte writes a few numbers per sprite into a typed array and the GPU expands them. Static scenes, where PixiJS can cache, are much closer.

Against Phaser 4 on the same laptop, spryte draws moving sprites 2.2-3.1x faster than Phaser's Image and 5x faster than its SpriteGPULayer, at 11.3 kB of JavaScript against Phaser's 276.6 kB (spryte vs Phaser). A Phaser game can keep Phaser and hand its heaviest effects to spryte, inside Phaser's frame and depth order, through an Extern (spryte/phaser, see the API docs). A PixiJS v8 app can do the same through a RenderContainer (spryte/pixi, API docs). Both share the engine's own GPU textures, so nothing is uploaded twice.

  • Bulk sprites with no per-sprite JavaScript. On WebGL2, pass a Float32Array of positions (plus optional per-copy scale, rotation and colour arrays) and spryte uploads it as is.
  • Immediate mode. There are no display objects to create, pool or destroy. Draw what you want this frame.
  • Verified output. Real-GPU pixel checks prove that the bulk paths, render targets and shaders draw exactly what the simple path draws.
// Particles: one call, no objects.
draw.sprites(spark, positions, count, { tint: "#ffd36b" }, { scale, rotation, color });

// Post-processing: draw into a target, then draw the target through a shader.
draw.into(scene, () => drawWorld(draw));
draw.withShader(crt, () => draw.sprite(scene, 0, 0, { origin: [0, 0] }));

Docs

Lifecycle

  • createRenderer(canvas, options) makes a Draw. It uses WebGL2 when available, and otherwise Canvas 2D, loaded on demand. await draw.ready waits until the fallback can draw.
  • You own the canvas size. After changing canvas.width or canvas.height, call draw.resize(w, h).
  • Each frame: draw.begin(...), then your draw calls in order, then draw.end(). Call order is draw order.
  • draw.invalidate(source) re-uploads an image whose pixels changed. draw.disposeTarget(t) frees a render target.
  • draw.dispose() releases every GPU resource and listener. Using a disposed Draw throws.
  • WebGL context loss is handled: drawing pauses while the context is lost (draw.contextLost), and textures re-upload after it's restored. Render targets come back blank. This is checked with a real GPU context loss in npm run test:browser.

Help and feedback

Questions, ideas and show-and-tell: Discord. Bugs: open an issue with your browser, GPU and a small repro.

Work with us

All of our projects are free and MIT licensed. Studios after performance help or a bigger partnership: [email protected].

Licence

MIT