Example · WebGL
A cube driven by tweens
The cube's rotation, tilt, and scale live in a plain object; GSAP tweens it like anything else. An infinite linear tween spins it, two quickTo setters tilt it toward the pointer, Pulse punches the scale, and the render loop is a gsap.ticker callback removed by the returned cleanup.
gsap.tickerquickTocleanup functions
move the pointer to tilt the cube
rot = { spin: 0.6, tiltX: 0, tiltY: -0.35, scale: 1 };
ref = injectGsap(({ gsap }) => {
// …compile shaders, upload the cube…
const render = () => {
gl.uniformMatrix4fv(uMvp, false, mvp(this.rot));
gl.drawArrays(gl.TRIANGLES, 0, 36);
};
gsap.ticker.add(render);
// endless spin on a plain object
gsap.to(this.rot, {
spin: '+=6.28', duration: 10,
repeat: -1, ease: 'none',
});
// one reusable tween per tilt axis
this.tiltToX = gsap.quickTo(this.rot, 'tiltX',
{ duration: 0.6, ease: 'power3' });
return () => gsap.ticker.remove(render);
});
pulse = this.ref.contextSafe(() =>
this.ref.gsap.fromTo(this.rot,
{ scale: 1.45 },
{ scale: 1, ease: 'elastic.out(1, 0.45)' })
);How this works
- There is no 3D library here, just a plain object
{ spin, tiltX, tiltY, scale }and a hand-rolled matrix. GSAP does not care: to it, rotation angles are properties over time like any CSS pixel. - The infinite spin is
gsap.to(rot, { spin: '+=6.28', repeat: -1, ease: 'none' }); the pointer tilt is twoquickTosetters, so hundreds of pointer events per second reuse one tween per axis. - Drawing happens in a
gsap.tickercallback, one shared frame loop with every other tween, outside change detection. The callback returns a cleanup and the context runs it on destroy, so the GL loop can't outlive the component.