# animation

Animation loop and transition helpers for browser environments.

> **Environment:** These functions rely on browser APIs (`requestAnimationFrame`, `cancelAnimationFrame`) and are intended for use in the main thread, not web workers.

---

## `animate(callback) → () => void`

Runs a callback on every animation frame in a continuous loop.

### Parameters

| Name | Type | Description |
|------|------|-------------|
| `callback` | `(time: number) => void` | Called on each frame. Receives the `DOMHighResTimeStamp` from `requestAnimationFrame`. |

### Returns

`() => void` — A stop function. Call it to cancel the animation loop.

### Example

```typescript
import { animate } from '@yaoc1996/js-tools'

const stop = animate((time) => {
    mesh.rotation.y = time * 0.001
    renderer.render(scene, camera)
})

// Stop after 5 seconds:
setTimeout(stop, 5000)
```

---

## `transition(from, to, setState, duration) → () => void`

Smoothly interpolates between two states over a given duration using `requestAnimationFrame`.

### Parameters

| Name | Type | Description |
|------|------|-------------|
| `from` | `T extends Record<string, number>` | The start state. All keys must map to numbers. |
| `to` | `T` | The end state. Must have the same keys as `from`. |
| `setState` | `(state: T) => void` | Called on each frame with the current interpolated state. |
| `duration` | `number` | Total transition duration in milliseconds. |

### Returns

`() => void` — A cancel function. Call it to abort the transition early.

### Example

```typescript
import { transition } from '@yaoc1996/js-tools'

const cancel = transition(
    { opacity: 0, scale: 0.5 },
    { opacity: 1, scale: 1 },
    (state) => applyStyle(element, state),
    300 // 300ms
)

// Abort if needed:
cancel()
```

### Notes

- Uses linear interpolation (`lerp`). Easing is not built in.
- The interpolation factor is clamped to `[0, 1]`, so `setState` is always called with values within the `from`–`to` range.

---

## `lazyAnimate(callback) → triggerUpdate`

Creates a lazy update loop driven by explicit trigger calls rather than a continuous frame loop. The callback is only run when a trigger is pending, and multiple rapid triggers are deduplicated into a single run.

Useful for re-rendering or updating something in response to state changes, where multiple synchronous updates should collapse into one async paint.

### Parameters

| Name | Type | Description |
|------|------|-------------|
| `callback` | `() => void \| Promise<void>` | Called when an update is needed. May be async. |

### Returns

`(onDone?: () => void) => Promise<void>` — A trigger function. Call it to request an update. Accepts an optional `onDone` callback that fires once the pending update chain settles.

### Example

```typescript
import { lazyAnimate } from '@yaoc1996/js-tools'

const triggerRender = lazyAnimate(async () => {
    await renderScene()
})

// Trigger from many places — only one render will happen per "batch":
someEvent.on('change', () => triggerRender())
anotherEvent.on('change', () => triggerRender())
```

### Notes

- The callback is invoked **immediately** (asynchronously) when `lazyAnimate` is first called.
- If multiple triggers arrive while the callback is running, only one additional run is scheduled (deduplication).
- Does not use `requestAnimationFrame` — timing is controlled by the Promise microtask queue.
- The trigger function can be awaited to know when the current update chain has settled.
