# Animation

> Redraw SilkGraphics cheaply every frame. Animate with damp, dampAngle, Spring and ease so motion feels the same at 60, 120 or 144 Hz.

Source: https://pixi-silk.schmooky.dev/docs/animation/

Live demo `spring` (source):

```ts
import { Container } from 'pixi.js';
import { damp, SilkGraphics, Spring } from 'pixi-silk';
import { defineDemo } from './runtime';

// Frame-rate independent motion: the knob glides the same at 60, 120 or 144 Hz.
export default defineDemo({
    size: [480, 200],
    touchAction: 'none',
    controls: {
        stiffness: { type: 'range', label: 'stiffness', min: 40, max: 400, step: 1, value: 170 },
        damping: { type: 'range', label: 'damping', min: 4, max: 40, step: 1, value: 14 },
    },
    setup({ stage, params, tick }) {
        const g = new SilkGraphics();
        const hit = new Container();
        const x = new Spring(120);
        let glow = 0;
        let dragging = false;

        hit.eventMode = 'static';
        hit.hitArea = { contains: (px: number, py: number) => px >= 0 && px <= 480 && py >= 0 && py <= 200 };
        hit.on('pointerdown', (e) => {
            dragging = true;
            x.target = Math.min(440, Math.max(40, e.getLocalPosition(stage).x));
        });
        hit.on('globalpointermove', (e) => {
            if (dragging) x.target = Math.min(440, Math.max(40, e.getLocalPosition(stage).x));
        });
        hit.on('pointerup', () => (dragging = false));
        hit.on('pointerupoutside', () => (dragging = false));
        stage.addChild(g, hit);
        tick((_t, dt) => {
            x.stiffness = params.stiffness;
            x.damping = params.damping;
            x.step(dt);
            glow = damp(glow, dragging ? 1 : 0, 10, dt);
            g.clear();
            g.pill(40, 94, 400, 12).fill(0x2c2c2e);
            g.pill(40, 94, Math.max(12, x.value - 40), 12).fill(0x0a84ff);
            g.circle(x.value, 100, 22 + glow * 6).fill({ color: 0x0a84ff, alpha: 0.35 * glow, blur: 10 });
            g.circle(x.value, 100, 20).fill(0xffffff);
        });
    },
});
```

## Immediate mode is cheap

The simplest way to animate is to clear and redraw every frame. `clear()` keeps the GPU buffers.
Each primitive is 40 floats, and one instanced draw call renders them all:

```ts
app.ticker.add((ticker) => {
    const t = ticker.lastTime / 1000;

    g.clear();
    g.arcSweep(80, 80, 44, -Math.PI / 2, Math.PI * 2 * (0.5 + 0.4 * Math.sin(t))).stroke({ width: 12, cap: 'round', color: 0x30d158 });
});
```

Put static parts in a second `SilkGraphics` and draw it once.

## Frame-rate independent motion

Easing by a fixed fraction per frame (`x += (target - x) * 0.1`) runs twice as fast on a 120 Hz
display. The helpers take the frame time instead:

```ts
import { damp, dampAngle, Spring, ease } from 'pixi-silk';

x = damp(x, target, 10, dt);              // exponential approach, lambda is about 1 / seconds
angle = dampAngle(angle, target, 8, dt);  // shortest way around the circle

const spring = new Spring(0, 170, 26);    // value, stiffness, damping
spring.target = 1;
app.ticker.add((t) => draw(spring.step(t.deltaMS / 1000)));

ease.outCubic(0.5); // inOutCubic, outCubic, inOutSine, outExpo
```

`Spring.step()` integrates with fixed sub-steps, so it stays stable with large frame times.
`spring.settled` tells you when to stop redrawing.

## Sub-pixel motion

Slow-moving shapes glide instead of snapping from pixel to pixel. Silk renders coordinates exactly
as given, and `createSilkApp` turns off pixel rounding. The [sub-pixel lab page](/lab/04-subpixel/)
compares Silk with tessellated graphics.
