AnimationLoop and time-driven rendering
Quick answer
This tutorial covers luma.gl's AnimationLoop, which waits for the Device, invokes a render callback each frame and releases resources on stop. It wires onInitialize, onRender, onFinalize and onError around an AnimationProps object exposing device, width, height, time, tick and tock, and builds a Model with a Buffer written each frame. It then adds setNeedsRedraw for static scenes. Readers can run a frame-rate independent, resource-clean render loop.
Key points
- Create buffers and models in onInitialize rather than inside onRender.
- Subtract props.startTime from props.time to obtain elapsed seconds.
- Call loop.setNeedsRedraw after camera, data or size changes occur.
Example
(async function () {
const device = await luma.luma.createDevice({
type: 'webgl',
adapters: [luma.webgl2Adapter],
createCanvasContext: {
canvas: document.getElementById('cv'),
useDevicePixels: Math.min(window.devicePixelRatio, 2),
autoResize: true
}
});
let model = null;
let positions = null;
const loop = new luma.AnimationLoop({
device: device,
onInitialize: function (props) {
positions = device.createBuffer({
data: new Float32Array(3 * 512),
usage: luma.Buffer.VERTEX | luma.Buffer.COPY_DST
});
model = new luma.Model(device, {
vs: vsSource,
fs: fsSource,
attributes: { positions: positions },
bufferLayout: [
{ name: 'positions', stepMode: 'vertex', format: 'float32x3' }
],
vertexCount: 512,
topology: 'line-strip'
});
console.log('初始画布尺寸', props.width, props.height);
},
onRender: function (props) {
const seconds = (props.time - props.startTime) / 1000;
if (props.tick % 2 === 0) {
const data = new Float32Array(3 * 512);
for (let i = 0; i < 512; i++) {
data[i * 3] = (i / 511) * 2 - 1;
data[i * 3 + 1] = Math.sin(i * 0.05 + seconds) * 0.4;
data[i * 3 + 2] = 0;
}
positions.write(data);
}
const pass = props.device.beginRenderPass({
clearColor: [0.05, 0.07, 0.10, 1]
});
model.draw(pass);
pass.end();
},
onFinalize: function () {
model.destroy();
positions.destroy();
},
onError: function (error) {
console.error('渲染出错', error);
}
});
await loop.start();
})();const loop = new luma.AnimationLoop({
device: device,
onRender: function (props) {
if (!loop.needsRedraw()) return;
const pass = props.device.beginRenderPass({ clearColor: [0, 0, 0, 1] });
model.draw(pass);
pass.end();
}
});
map.on('move', function () {
updateViewMatrix();
loop.setNeedsRedraw('camera-changed');
});
Notes
- stop() halts the loop but does not destroy models or buffers.
- needsRedraw() clears its flag when read, so mark the first frame explicitly.
English edition written for Atlaslog. Code is taken from the original Chinese page and runs unchanged.
Atlaslog (2026). AnimationLoop and time-driven rendering. https://www.jingweizhi.com/en/lumagl/tutorials/animation-loop.html (accessed 2026-11-03).