Skip to content
GitHub repository

PixiJS v8WebGL2TypeScriptMIT

Smooth, anti-aliased graphics for PixiJS v8, smooth as silk.

pixi-silk draws each PixiJS shape as an exact signed distance field. Every edge is anti-aliased per pixel, so it stays smooth at any zoom, rotation and pixel ratio. No MSAA, no triangles, one draw call per object. Squircles, arcs, smooth lines, dashes, OKLab gradients and glows are included.

Get startedBrowse the lab

What is pixi-silk?

pixi-silk is an open-source TypeScript library that adds smooth, anti-aliased vector graphicsto PixiJS v8. Its SilkGraphics object has the same shape, fill and stroke API as Pixi's Graphics. It does not cut shapes into triangles. For every pixel, it computes the exact signed distance to the shape and turns it into coverage over one device pixel. This isanalytic anti-aliasing. It is exact at any scale, in filters and render textures, and on retina screens with fractional pixel ratios.

draw call per SilkGraphics
1
draw call per SilkGraphics
canvas to device pixels
1:1
canvas to device pixels
dithered gradient ramps
16-bit
dithered gradient ramps
MSAA, filters or extra passes
0
MSAA, filters or extra passes

Graphics-like API, per-pixel precision

SilkGraphics reads like Pixi'sGraphics: add shapes to the path, then fill or stroke them. Each primitive is one instanced quad. Its fragment shader computes the exact distance to the shape for every pixel.

  • Works for UI, charts, icons and widgets
  • Clearing and redrawing every frame is cheap
  • Every guide has live demos with their source
Read the introduction
Source src/demos/quick-start.demo.ts
import { conic, SilkGraphics, vertical } from 'pixi-silk';
import { defineDemo } from './runtime';

const GAUGE = conic([0x30d158, 0xffd60a, 0xff453a]);
const FILL = vertical(
    [
        [0, 0xff9f0a, 0.8],
        [1, 0xff9f0a, 0],
    ],
    { easing: 'smooth' },
);

export default defineDemo({
    size: [360, 160],
    controls: {
        progress: { type: 'range', label: 'progress', min: 0, max: 1, step: 0.01, value: 0.72 },
    },
    setup({ stage, params, tick }) {
        const g = new SilkGraphics();

        stage.addChild(g);
        tick((t) => {
            const wave = [120, 100, 170, 80, 220, 92, 270, 60, 320, 70].map((v, i) =>
                i % 2 ? v + Math.sin(t + i) * 6 : v,
            );

            g.clear();
            g.roundRect(10, 10, 340, 140, 22, 0.6).fill(0x1c1c1e); // squircle card
            g.arcSweep(80, 80, 44, -Math.PI / 2, Math.PI * 2).stroke({ width: 12, color: 0x2c2c2e });
            g.arcSweep(80, 80, 44, -Math.PI / 2, Math.PI * 2 * params.progress).stroke({
                width: 12,
                cap: 'round',
                gradient: GAUGE,
            }); // gauge follows the arc
            g.area(wave, 130, { smooth: 'monotone' })
                .fill(FILL) // chart fill ...
                .stroke({ width: 2, color: 0xff9f0a, cap: 'round' }); // ... and its top line
        });
    },
});

Everything is smooth

Exact anti-aliasing

Each pixel gets its coverage from the exact distance to the shape. Smooth at any zoom, rotation and DPR, in filters and render textures too.

One draw call

Each SilkGraphics is one instanced draw call. Rebuilding 50,000 primitives takes about 12 ms of CPU per frame.

Squircles and more shapes

Smooth squircle corners, per-corner radii, arcs, donut sectors, hearts, stars, rounded triangles and chart areas.

OKLab gradients

Linear, radial, conic and along-the-path gradients. Mixed in OKLab, stored as half floats and dithered, so they do not band.

Glows and soft shadows

Blur any fill or stroke. Neon glows, drop shadows and motion blur cost no filter and no extra pass.

Dashes, dots, tapers

Dashes and dots with caps, drawn in the shader. Closed outlines have no seam. Strokes can taper along the path.

DPR-exact canvas

createSilkApp maps canvas pixels 1:1 to device pixels, even at 1.5x or 2.625x, and follows DPR changes.

Real hit testing

Pointer events test the real shape. Rings are hollow and a line only reacts on its stroke.

Source src/demos/versus.demo.ts
import { Container, Graphics, Text } from 'pixi.js';
import { SilkGraphics } from 'pixi-silk';
import { defineDemo } from './runtime';

// Left: Pixi v8 Graphics (tessellated, no MSAA). Right: SilkGraphics (distance fields).
export default defineDemo({
    size: [480, 240],
    controls: {
        zoom: { type: 'range', label: 'zoom', min: 1, max: 6, step: 0.01, value: 1 },
        spin: { type: 'toggle', label: 'rotate', value: true },
    },
    setup({ stage, params, tick }) {
        const sides = [new Container(), new Container()];
        const pixi = new Graphics();
        const silk = new SilkGraphics();

        sides[0].addChild(pixi);
        sides[1].addChild(silk);
        sides.forEach((side, i) => {
            const mask = new Graphics().rect(i * 240, 0, 240, 240).fill(0xffffff);
            const label = new Text({
                text: i ? 'SilkGraphics' : 'Graphics',
                style: { fill: 0x8e8e93, fontSize: 12, fontFamily: 'system-ui' },
            });

            label.position.set(i * 240 + 12, 10);
            side.mask = mask;
            stage.addChild(side, mask, label);
        });
        let angle = 0;

        tick((_t, dt) => {
            if (params.spin) angle += dt * 0.15;
            for (const [i, side] of sides.entries()) {
                side.pivot.set(120, 130);
                side.position.set(i * 240 + 120, 130);
                side.scale.set(params.zoom);
                side.rotation = angle;
            }
            for (const g of [pixi, silk]) {
                g.clear();
                g.circle(120, 130, 62).stroke({ width: 3, color: 0x64d2ff });
                g.roundRect(70, 90, 100, 80, 24).stroke({ width: 1, color: 0xffffff });
                g.circle(120, 130, 18).fill(0xff375f);
                for (let k = 0; k < 6; k++)
                    g.moveTo(40, 60 + k * 3)
                        .lineTo(200, 64 + k * 5)
                        .stroke({ width: 0.6, color: 0xffd60a });
            }
        });
    },
});

Why v8 stopped looking smooth

PixiJS v8 turns Graphics into triangles once, when you draw. Scale it up and curves show facets. Thin lines crawl between pixel rows. Anti-aliasing is MSAA on the main canvas only: filters and render textures stay aliased unless you opt each one in, and filters render at resolution 1. Silk computes coverage per pixel, so none of this applies.

How it works

Rebuilt pixel for pixel

All five sheets

Five design reference sheets and 81 widgets, measured against the originals. Left is the reference, right is the live render.

Live Activities reference design next to its smooth, anti-aliased pixi-silk rebuild in PixiJS v8

Questions

How do I enable anti-aliasing in PixiJS v8?

Pass antialias: true to app.init() for MSAA on the main canvas. For sharp output on retina screens, add resolution: window.devicePixelRatio. Set autoDensity: true too. MSAA uses four samples per pixel. It skips filters and render textures unless you opt them in. For exact anti-aliasing everywhere, draw vector shapes with SilkGraphics from pixi-silk and keep antialias: false.

Why do PixiJS v8 Graphics look jagged or blurry?

PixiJS v8 tessellates Graphics into triangles when you draw, and only MSAA (antialias: true) anti-aliases them. Scaled curves show facets, and thin lines crawl between pixels. Filters and render textures stay aliased unless you opt them into MSAA. Filters default to resolution 1, which blurs them on retina screens. pixi-silk replaces the triangles with exact per-pixel distance fields.

What is pixi-silk?

pixi-silk is an MIT-licensed TypeScript library for PixiJS v8. Its SilkGraphics display object has a Graphics-like API. It draws rects, squircles, circles, arcs, lines, paths, areas and more as signed distance fields with analytic anti-aliasing. You also get OKLab gradients, glows, dashes and a DPR-exact app helper, createSilkApp.

What is analytic anti-aliasing?

Analytic anti-aliasing computes each edge pixel's coverage exactly from the shape's geometry, not from a few samples. For each pixel, pixi-silk divides the signed distance to the shape by the size of one device pixel. Edges stay smooth and exact at any zoom, rotation and resolution.

MSAA or SDF anti-aliasing: which is better for 2D graphics?

SDF (signed distance field) anti-aliasing is better for vector UI, charts and icons. It gives continuous coverage, not MSAA's five levels per pixel. It handles hairlines and zoom, and works inside filters and render textures without extra memory. MSAA is simpler for arbitrary triangle meshes.

All questions