SuperAPI AI Directory

gsap-optimiser

Frontend & websites

View repository

Updated 10-15 22:38

About this skill

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) => {
    // Scroll-driven (auto-collected)
    gsap.to('.item', { y: 0, scrollTrigger: { ... } })
// One-time setup (auto-collected)
gsap.set(el, { transformPerspective: 900 })

// Event-handler tweens (collected via ctx.add)
self.add('onHover', (el, x, y) => {
  gsap.to(el, { rotationX: x, rotationY: y, overwrite: 'auto' })
})

}, scopeRef.value) })

onUnmounted(() => ctx?.revert()) // kills ALL tweens + ScrollTriggers + restores styles


**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

```js
// GOOD — GPU-composited, no layout recalc
gsap.to(el, { x: 100, y: 50, rotation: 45, scale: 1.2, opacity: 0.8 })

// BAD — triggers layout/reflow on every frame
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 })  // repeated interaction target
gsap.to(img, { scale: 1, force3D: false }) // crisp image, no blur

3. will-change Lifecycle

Set on interaction start, release when animation completes. Never permanent in CSS.

self.add('applyEffect', (el) => {
  el.style.willChange = 'transform'          // promote to GPU layer
  gsap.to(el, { scale: 1.03, ...HOVER_IN })
})

self.add('resetEffect', (el) => {
  gsap.to(el, {
    scale: 1, ...HOVER_OUT,
    onComplete: () => { el.style.willChange = 'auto' }  // release layer
  })
})

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')

// inside ticker or mousemove
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 })                    // hidden + invisible
gsap.to(els, { autoAlpha: 1, duration: 0.8 })      // fades in, sets visibility: inherit

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,
})

// Usage
gsap.effects.fadeIn('.cards')
tl.fadeIn('.cards', { duration: 0.4 })  // on timelines too

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',    // default GPU behaviour
  autoSleep: 120,     // frames before ticker sleeps (saves mobile battery)
  nullTargetWarn: false, // silence null-target warnings in production
})

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)  // default — usually fine
gsap.ticker.lagSmoothing(0)        // disable for frame-perfect needs (video sync)

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

// BAD: bare gsap.to inside event handler — orphaned tween
el.addEventListener('mousemove', () => gsap.to(el, { x: 10 }))

// BAD: gsap.to in rapid fire without overwrite — tween pile-up
onMouseMove = () => gsap.to(el, { x: pos, duration: 0.3 })

// BAD: layout properties — triggers reflow
gsap.to(el, { width: 200, height: 100, left: 50 })

// BAD: transformPerspective on every mousemove frame
gsap.to(el, { transformPerspective: 900, rotationX: x })

// BAD: manual scrollTriggers[] alongside gsap.context
const scrollTriggers = [] // redundant — ctx.revert() collects them

// BAD: permanent will-change in CSS
.card { will-change: transform; } // never released, wastes GPU memory

// BAD: new timeline every call
const show = () => gsap.timeline().to(el, { ... }) // recreates each time

// BAD: individual ScrollTrigger per element (use batch instead)
document.querySelectorAll('.item').forEach(item => {
  gsap.to(item, { scrollTrigger: { trigger: item, ... }, ... })
})

Checklist

Apply to every GSAP-using component:

Cleanup

  • All tweens inside gsap.context() or ctx.add() handlers
  • ctx.revert() called in onUnmounted
  • No manual scrollTriggers[] array
  • Separate context when composable coexistence required

GPU & Rendering

  • Only transform/opacity properties animated (no left/top/width/height)
  • force3D: true on repeatedly animated elements; "auto" elsewhere
  • will-change set on interaction start, released in onComplete
  • autoAlpha used instead of opacity for show/hide
  • transformPerspective set once via gsap.set(), not per-frame

High-Frequency

  • overwrite: 'auto' on all rapid-fire tweens
  • quickTo() for repeated animated cursor/scroll updates
  • quickSetter() for per-frame direct value piping

ScrollTrigger

  • ScrollTrigger.batch() for many similar elements
  • once: true on one-shot triggers
  • invalidateOnRefresh: true on responsive layouts
  • scrub uses smoothing number (e.g. 0.5) not bare true when possible
  • markers removed in production

Organisation

  • Shared tween defaults extracted to constants
  • Reusable animations registered via gsap.registerEffect()
  • Timelines created once and played/reversed, not recreated

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()