Renderer
A Renderer provides an automatic render loop for a Scene, powered by requestAnimationFrame. It continuously re-renders the scene each frame, enabling smooth animations and interactive effects. The renderer also provides a transition() method for animating element properties.
The Renderer is what brings your scene to life. Without it, you'd need to manually call scene.render() every time something changes. With a Renderer, you describe what should change (via transitions) and the renderer handles when and how, including easing, chaining, staggering, and automatic start/stop to conserve resources when idle.
Demo
Click "Animate" to run a transition. Click "Stagger" to see per-element staggered animation.
Creating a Renderer
import {
createRenderer,
createScene,
} from '@ripl/web';
const scene = createScene('.my-container', {
children: [circle, rect],
});
const renderer = createRenderer(scene);Options
The renderer accepts autoStart (default true) and autoStop (default true). With autoStop, the renderer pauses when idle and restarts when needed:
const renderer = createRenderer(scene, {
autoStart: false,
autoStop: false,
});Debug Mode
The renderer includes built-in debug overlays to help you inspect and profile your scene. Pass the debug option to createRenderer to enable them.
Setting debug: true enables all overlays at once:
const renderer = createRenderer(scene, {
debug: true,
});For granular control, pass an object with the specific overlays you want:
const renderer = createRenderer(scene, {
debug: {
fps: true, // frames-per-second counter
elementCount: true, // number of elements in the scene buffer
boundingBoxes: true, // red outline around every element's bounding box
},
});| Option | Description |
|---|---|
fps | Displays a smoothed frames-per-second counter in an overlay badge |
elementCount | Shows the total number of elements rendered each frame |
boundingBoxes | Draws a red stroke rectangle around every element's bounding box |
When fps or elementCount is enabled, a semi-transparent overlay badge appears in the top-left corner of the scene.
boundingBoxes draws as a post-pass over the whole scene buffer, in world space, so each outline follows the element's rendered position — including its own translate, rotate and scale and every ancestor group's transform. A rotated element yields a conservative axis-aligned box that encloses the rotated content, matching what getBoundingBox() reports. Only leaf elements are outlined; groups are not.
Demo
Toggle the debug overlays below to see them in action.
Render Loop
When running, the renderer executes a tick on every animation frame:
- Clears the context
- Advances all active transitions
- Renders every element in the scene buffer
- Requests the next frame
With autoStop enabled (default), the renderer automatically stops when there are no active transitions and the mouse leaves the scene. It restarts when the mouse re-enters or a new transition is created.
Transitions
The transition() method smoothly animates element properties from their current values to new target values. It returns a Promise-like Transition that resolves when the animation completes.
Basic Transition
await renderer.transition(circle, {
duration: 1000,
state: {
radius: 100,
fill: '#ff006e',
},
});Easing Functions
Ripl provides a full set of easing functions:
import {
easeInCubic,
easeInOutCubic, easeInOutQuad, easeInOutQuart,
easeInOutQuint, easeInQuad, easeInQuart,
easeInQuint, easeLinear, easeOutCubic,
easeOutQuad, easeOutQuart, easeOutQuint,
} from '@ripl/web';
await renderer.transition(circle, {
duration: 800,
ease: easeOutCubic,
state: { radius: 100 },
});Chaining Transitions
Because transition() returns a promise, you can chain animations sequentially:
await renderer.transition(circle, {
duration: 500,
ease: easeOutCubic,
state: { radius: 100 },
});
await renderer.transition(circle, {
duration: 500,
ease: easeOutCubic,
state: { radius: 50 },
});Animating Multiple Elements
Pass an array of elements to animate them all with the same options:
await renderer.transition([circle, rect], {
duration: 800,
ease: easeOutCubic,
state: { fill: '#ff006e' },
});Per-Element Options
Pass a function instead of an options object to customize the transition per element. This is useful for staggered animations:
await renderer.transition(group.graph(false), (element, index, total) => ({
duration: 500,
delay: index * 100, // stagger by 100ms
ease: easeOutCubic,
state: { fill: '#ff006e' },
}));Passing a group directly to transition animates the group itself as a single unit (its own transform and opacity, composited at the group boundary), not its children. To animate a group's descendants (for example, to stagger across them), flatten it first with group.graph(false).
Manual Control
start()
Manually start the render loop:
renderer.start();stop()
Manually stop the render loop. Clears all active transitions:
renderer.stop();Events
The renderer emits start and stop events to track the render loop lifecycle:
renderer.on('start', (event) => {
console.log('Renderer started at', event.data.startTime);
});Cleanup
The renderer automatically destroys itself when the scene is destroyed. You can also destroy it manually:
renderer.destroy(); // stops the loop and cleans upNOTE
For the full list of Renderer properties, methods, and events, see the Renderer API Reference.