| name | gsap-optimiser |
| description | Comprehensive GSAP performance optimisation for Vue 3 / Nuxt 3. Covers: context cleanup (gsap.context, ctx.add, ctx.revert), GPU acceleration (force3D, transforms vs layout), will-change lifecycle, high-frequency animation tools (quickTo, quickSetter, overwrite), ScrollTrigger tuning (batch, scrub, once, fastScrollEnd, invalidateOnRefresh, normalizeScroll, matchMedia), autoAlpha, lazy rendering, timeline reuse, registerEffect, gsap.defaults, ticker/lagSmoothing, and anti-patterns. Triggers: GSAP performance, animation optimisation, gsap.to, gsap.timeline, ScrollTrigger, memory leak, tween cleanup, mousemove animation, will-change, force3D, quickSetter, quickTo, Vue GSAP, Nuxt GSAP, ctx.add, gsap.context, GPU compositing.
|
GSAP Optimiser — Vue / Nuxt
Split structure: references/performance-deep-dive.md for extended API detail, references/vue-examples.md for full component code.
1. Context & Cleanup
Rule: Every gsap.to() / gsap.fromTo() / gsap.timeline() must live inside a gsap.context() — directly or via ctx.add().
let ctx
onMounted(() => {
ctx = gsap.context((self) => {
gsap.to('.item', { y: 0, scrollTrigger: { ... } })
gsap.set(el, { transformPerspective: 900 })
self.add('onHover', (el, x, y) => {
gsap.to(el, { rotationX: x, rotationY: y, overwrite: 'auto' })
})
}, scopeRef.value)
})
onUnmounted(() => ctx?.revert())
Composable coexistence: When useReveal() (own internal context) coexists with interactive animations, create a separate context for the interactive part.
2. GPU Acceleration
Transforms only — never layout properties
gsap.to(el, { x: 100, y: 50, rotation: 45, scale: 1.2, opacity: 0.8 })
gsap.to(el, { left: 100, top: 50, width: '120%', height: 200 })
force3D
Defaults to "auto" — uses translate3d() during animation (promotes to compositor layer), switches back to 2D when done to free GPU memory.
| Value | Behaviour |
|---|
"auto" | 3D during tween, 2D after — default, usually best |
true | Stay 3D permanently — use for elements animated repeatedly (mousemove) |
false | Always 2D — avoids sub-pixel blurring on upscaled images |
gsap.to(el, { x: 100, force3D: true })
gsap.to(img, { scale: 1, force3D: false })
3. will-change Lifecycle
Set on interaction start, release when animation completes. Never permanent in CSS.
self.add('applyEffect', (el) => {
el.style.willChange = 'transform'
gsap.to(el, { scale: 1.03, ...HOVER_IN })
})
self.add('resetEffect', (el) => {
gsap.to(el, {
scale: 1, ...HOVER_OUT,
onComplete: () => { el.style.willChange = 'auto' }
})
})
4. High-Frequency Animations
overwrite: 'auto'
Kills in-progress tweens on the same target/properties. Required on all rapid-fire tweens (mousemove, scroll callbacks).
gsap.to(el, { x: pos, overwrite: 'auto', duration: 0.3 })
gsap.quickTo() — animated updates (50-250% faster)
Pre-creates a reusable tween function. Ideal for cursor followers, parallax.
const xTo = gsap.quickTo('#cursor', 'x', { duration: 0.4, ease: 'power3' })
const yTo = gsap.quickTo('#cursor', 'y', { duration: 0.4, ease: 'power3' })
el.addEventListener('mousemove', (e) => { xTo(e.clientX); yTo(e.clientY) })
gsap.quickSetter() — immediate updates (no animation)
Skips unit conversion, relative values, parsing — raw speed for per-frame sets.
const setX = gsap.quickSetter('#el', 'x', 'px')
const setY = gsap.quickSetter('#el', 'y', 'px')
setX(mouseX); setY(mouseY)
When to use which:
| Method | Animates? | Speed | Use case |
|---|
gsap.to() | Yes | Normal | General animation |
gsap.quickTo() | Yes | 50-250% faster | Repeated cursor/scroll-driven animation |
gsap.quickSetter() | No (instant) | Fastest | Per-frame direct value piping |
gsap.set() | No (instant) | Normal | General one-off sets |
5. Lazy Rendering & Layout Thrashing
GSAP defers first-render writes to the end of the current tick — avoids read/write/read/write layout thrashing automatically. Default lazy: true.
- Do not set
lazy: false unless you have a specific reason.
- Zero-duration tweens (
duration: 0) have lazy: false by default.
autoAlpha vs opacity
autoAlpha is identical to opacity but also toggles visibility: hidden when value reaches 0 — prevents pointer events and improves browser rendering of invisible elements.
gsap.set(els, { autoAlpha: 0 })
gsap.to(els, { autoAlpha: 1, duration: 0.8 })
Use autoAlpha instead of opacity for reveal animations.
6. ScrollTrigger Optimisation
batch() — staggered reveal of many elements
Creates one ScrollTrigger per target, batches callbacks within a time window.
ScrollTrigger.batch('.animate-item', {
onEnter: (els) => gsap.to(els, { autoAlpha: 1, y: 0, stagger: 0.1, overwrite: true }),
onLeave: (els) => gsap.to(els, { autoAlpha: 0, y: -20, overwrite: true }),
})
Key properties
| Property | What it does |
|---|
scrub: true | Links progress 1:1 with scroll — use only for transform/opacity |
scrub: 0.5 | Smooth catch-up (0.5s lag) — less jittery than true |
once: true | Fires once then self-destructs — less memory |
fastScrollEnd: true | Snaps to end state if user scrolls out fast |
invalidateOnRefresh: true | Recalculates start values on resize |
markers: true | Dev only — remove for production |
normalizeScroll()
Forces scroll onto JS thread — consistent mobile behaviour, prevents address-bar jank.
ScrollTrigger.normalizeScroll(true)
matchMedia — responsive animations
Different animations per breakpoint — auto-reverts when breakpoint changes.
const mm = gsap.matchMedia()
mm.add('(min-width: 1024px)', () => {
gsap.to(el, { x: 500, scrollTrigger: { trigger: el, start: 'top center' } })
})
mm.add('(max-width: 1023px)', () => {
gsap.to(el, { y: 200, scrollTrigger: { trigger: el, start: 'top 80%' } })
})
7. Timeline Reuse & registerEffect
Reuse timelines — don't recreate
const tl = gsap.timeline({ paused: true })
tl.to(el, { opacity: 1, duration: 0.3 }).to(el, { y: -20, duration: 0.5 })
const show = () => tl.play()
const hide = () => tl.reverse()
registerEffect — DRY reusable animations
gsap.registerEffect({
name: 'fadeIn',
effect: (targets, config) => gsap.to(targets, { autoAlpha: 1, y: 0, duration: config.duration, stagger: config.stagger }),
defaults: { duration: 0.6, stagger: 0.08 },
extendTimeline: true,
})
gsap.effects.fadeIn('.cards')
tl.fadeIn('.cards', { duration: 0.4 })
8. Global Configuration
gsap.defaults() — inherited by every tween
gsap.defaults({ ease: 'power2.out', duration: 0.5 })
gsap.config() — engine-level settings
gsap.config({
force3D: 'auto',
autoSleep: 120,
nullTargetWarn: false,
})
Ticker & lagSmoothing
GSAP's ticker syncs with requestAnimationFrame. lagSmoothing handles CPU spikes gracefully — if the browser lags >500ms, it acts as if only 33ms passed.
gsap.ticker.lagSmoothing(500, 33)
gsap.ticker.lagSmoothing(0)
9. Shared Tween Defaults Pattern
const HOVER_IN = { duration: 0.35, ease: 'power2.out', overwrite: 'auto' }
const HOVER_OUT = { duration: 0.7, ease: 'elastic.out(1, 0.4)' }
gsap.to(el, { x: 10, force3D: true, ...HOVER_IN })
gsap.to(el, { x: 0, force3D: true, ...HOVER_OUT, onComplete: () => { el.style.willChange = 'auto' } })
10. Anti-Patterns
el.addEventListener('mousemove', () => gsap.to(el, { x: 10 }))
onMouseMove = () => gsap.to(el, { x: pos, duration: 0.3 })
gsap.to(el, { width: 200, height: 100, left: 50 })
gsap.to(el, { transformPerspective: 900, rotationX: x })
const scrollTriggers = []
.card { will-change: transform; }
const show = () => gsap.timeline().to(el, { ... })
document.querySelectorAll('.item').forEach(item => {
gsap.to(item, { scrollTrigger: { trigger: item, ... }, ... })
})
Checklist
Apply to every GSAP-using component:
Cleanup
GPU & Rendering
High-Frequency
ScrollTrigger
Organisation
References
references/performance-deep-dive.md — Extended API details for quickTo, quickSetter, ScrollTrigger.batch, matchMedia, registerEffect, ticker
references/vue-examples.md — Full Vue/Nuxt component implementations:
- 3D tilt card with composable coexistence (separate contexts)
- Multi-concept page with all event handlers in a single context via ctx.add()