GSAP for tweens
GSAP (GreenSock Animation Platform) is the de facto JavaScript animation library. Pair it with Three.js for camera moves, object tweens, scroll-triggered scenes, and timelines — you get butter-smooth motion with semantics richer than handwritten animation code.
gsap.to, timelines, scrollTrigger, three
EXAMPLE
// 1) Install
// npm install gsap
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
gsap.registerPlugin(ScrollTrigger);
// 2) Three.js boilerplate
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, innerWidth/innerHeight, 0.1, 100);
camera.position.set(0, 0, 5);
const controls = new OrbitControls(camera, renderer.domElement);
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0x4f46e5 }),
);
scene.add(cube);
scene.add(new THREE.DirectionalLight(0xffffff, 1).position.set(2, 3, 5));
function loop() {
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(loop);
}
loop();
// 3) Single tween — animate position
gsap.to(cube.position, {
x: 2, y: 1, duration: 1.5, ease: 'power2.out',
});
// gsap mutates plain objects (Vector3 is just { x, y, z }). It interpolates each prop frame by frame.
// 4) Rotation + scale
gsap.to(cube.rotation, { x: Math.PI * 2, duration: 2, ease: 'sine.inOut' });
gsap.to(cube.scale, { x: 2, y: 2, z: 2, duration: 1, yoyo: true, repeat: 1 });
// 5) Material properties
gsap.to(cube.material.color, { r: 1, g: 0.3, b: 0.3, duration: 1 });
gsap.to(cube.material, { opacity: 0.3, duration: 1 });
cube.material.transparent = true;
// 6) Camera moves — lookAt requires re-evaluation each frame
const cameraTarget = new THREE.Vector3(0, 0, 0);
gsap.to(camera.position, {
x: 5, y: 3, z: 8,
duration: 2,
ease: 'power2.inOut',
onUpdate: () => camera.lookAt(cameraTarget),
});
// 7) Timeline — sequence + parallelism
const tl = gsap.timeline({ defaults: { ease: 'power2.out', duration: 1 } });
tl.to(cube.position, { x: 2 })
.to(cube.position, { y: 1 }, '-=0.5') // overlap by 0.5s
.to(cube.rotation, { x: Math.PI }, '<') // start with previous
.to(cube.material.color, { g: 1 }, 'colorShift') // label
.to(cube.scale, { x: 2, y: 2, z: 2 }, 'colorShift+=0.2'); // start 0.2s after label
tl.play();
tl.pause();
tl.reverse();
tl.seek(2);
tl.timeScale(0.5); // half speed
// 8) ScrollTrigger — scroll-driven scenes
gsap.to(cube.rotation, {
y: Math.PI * 4,
scrollTrigger: {
trigger: '#hero-section',
start: 'top center',
end: 'bottom top',
scrub: true, // tie to scroll position
markers: true,
},
});
// 9) Pinning a canvas + animating across scroll
gsap.timeline({
scrollTrigger: {
trigger: '#scene',
start: 'top top',
end: '+=2000',
pin: true,
scrub: 1,
},
})
.to(camera.position, { z: 2 })
.to(camera.position, { x: 3 })
.to(cube.rotation, { y: Math.PI, x: Math.PI }, '<');
// 10) Easing — GSAP has a great picker
// power1/2/3/4 in/out/inOut
// sine in/out/inOut
// expo in/out/inOut
// back in/out/inOut (overshoot)
// elastic in/out
// bounce in/out
// steps(n)
// CustomEase (paid plugin)
// 11) Stagger — animate many objects in sequence
const cubes = [];
for (let i = 0; i < 10; i++) {
const m = new THREE.Mesh(new THREE.BoxGeometry(0.3, 0.3, 0.3), new THREE.MeshStandardMaterial({ color: 0xffffff }));
m.position.set(i - 5, -2, 0);
scene.add(m); cubes.push(m);
}
gsap.to(cubes.map((c) => c.position), {
y: 0,
duration: 0.6,
ease: 'back.out(1.7)',
stagger: { each: 0.1, from: 'center' },
});
// 12) Numeric tweens — animate a plain object
const progress = { v: 0 };
gsap.to(progress, {
v: 1, duration: 2, onUpdate: () => {
cube.material.emissiveIntensity = progress.v * 2;
},
});
// 13) Pause + resume on visibility
const stContext = gsap.context(() => {
gsap.to(cube.position, { x: 3, duration: 2 });
});
// stContext.revert() — undo all animations created within the context (great for SPA cleanup).
// 14) Reduced motion
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!reduce) {
gsap.to(cube.position, { x: 2, duration: 1 });
}
// 15) Performance + safety
// • Use 'will-change: transform' on the canvas for layer promotion
// • Avoid creating new tweens every frame (re-target instead with .to() that has overwrite: 'auto')
// • For 1000+ tweens, use timelines or animate a single proxy object
// • Dispose tweens on unmount with gsap.context() or ScrollTrigger.killAll()
// 16) Common bugs
// • Tween targets a Vector3 but updates not reflected — Three uses Vector3.set; gsap mutates fields directly — both work
// • Camera follows new position but never updates lookAt — use onUpdate
// • ScrollTrigger not firing — registerPlugin missed, or DOM target not in viewport
// • Refreshing layout after content loads — call ScrollTrigger.refresh()
// • Multiple tweens on the same property — last write wins or visual stutter; set overwrite: 'auto'
// • Tweens after route change in SPA — old tweens still running; use gsap.context() + ctx.revert()
// • Forgot to handle prefers-reduced-motion — accessibility miss
Why it matters
GSAP turns Three.js animations into named timelines you can scrub, label, stagger, and tie to scroll. Animate position, rotation, scale, and material properties directly; update camera.lookAt inside onUpdate; clean up with gsap.context() on SPA route changes; and honour prefers-reduced-motion for accessibility.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import gsap from 'gsap';
gsap.to(mesh.position, { x: 5, duration: 1.5, ease: 'power2.out' });
Try it Yourself »
Discussion
Loading…