Lesson 15 · Debug Playground

5 common bugs,
fixed live.

Each panel shows a broken animation and its fix. Press "Buggy" to see the problem, "Fixed" to see it corrected.

Bug 01

autoAlpha vs opacity

Element set invisible in CSS, GSAP tries to show it — but it stays hidden because of inline style conflicts.

Animation target
Hello, world!
Before (broken)
// opacity:0 set in HTML style=""
// GSAP tries to animate from opacity:0
gsap.from('#box1', {
  opacity: 0,   // ← conflicts with
  y: 20,        //   inline style
  duration: 0.5
});
After (fixed)
// Use autoAlpha: GSAP manages
// both opacity AND visibility
gsap.from('#box1', {
  autoAlpha: 0, // ← handles lifecycle
  y: 20,
  duration: 0.5
});

Why it happens: autoAlpha sets visibility: hidden at start and removes the inline style on completion. Plain opacity leaves the inline style behind and fights with CSS.

Bug 02

ScrollTrigger not firing

start: 'top top' only fires when the element's top edge reaches the very top of the viewport — often too late or never.

Animation target
Scroll trigger
Waiting...
Before (broken)
gsap.from('#box2', {
  scrollTrigger: {
    trigger: '#box2',
    start: 'top top', // ← fires too late
  },
  x: -60,
  opacity: 0,
  duration: 0.6,
});
After (fixed)
gsap.from('#box2', {
  scrollTrigger: {
    trigger: '#box2',
    start: 'top 80%', // ← fires early
  },                  //   enough
  x: -60,
  opacity: 0,
  duration: 0.6,
});

Why it happens: 'top top' means the element's top must reach the viewport's top. For elements partway down the page, this never happens during normal scrolling. Use 'top 80%' to fire earlier. Add markers: true during development to see exactly where triggers are.

Bug 03

from vs to confusion

gsap.to('.box', { x: 0 }) — box is already at x:0, so nothing happens. The most common "why won't it move?" bug.

Animation target
Nothing moves
Before (broken)
// Animates TO x:0, opacity:1
// but the box is already there!
gsap.to('#box3', {
  x: 0,         // ← already 0
  opacity: 1,   // ← already 1
  duration: 0.6,
  ease: 'power2.out',
});
After (fixed)
// Animates FROM x:-60 TO its
// natural (current) position
gsap.from('#box3', {
  x: -60,       // ← start here
  opacity: 0,   // ← fade in
  duration: 0.6,
  ease: 'power2.out',
});

Why it happens: gsap.to() animates to the values you provide. gsap.from() animates from the values you provide, ending at the element's current natural position. For entrance animations, you almost always want from.

Bug 04

DOM timing

querySelectorAll called before elements exist returns an empty NodeList. GSAP silently animates nothing.

Animation target
Item A
Item B
Item C
Before (broken)
// Script in <head>, runs before
// .list-item elements exist
const items =
  document.querySelectorAll('.list-item');
// items.length === 0 ← empty!
gsap.from(items, {
  y: 20,
  opacity: 0,
  stagger: 0.1,
});
After (fixed)
// Wait for DOM to be ready
window.addEventListener(
  'DOMContentLoaded',
  () => {
    gsap.from('.list-item', {
      y: 20,
      opacity: 0,
      stagger: 0.1,
      duration: 0.5,
    });
  }
);

Why it happens: If <script> runs in <head> before the HTML below it is parsed, the elements don't exist yet. Fix: wrap in DOMContentLoaded, or place your script tag at the end of <body>.

Bug 05

Timeline position collision

Using '-=2' on a tween that only lasts 0.4s pushes it to negative time. The tween never plays.

Animation target
A
B
Before (broken)
// A lasts 0.4s. Using '-=2' pushes
// B to t=-1.6 → clipped, never plays
tl.from('#tl5a', {
  opacity: 0,
  duration: 0.4,
})
.from('#tl5b', {
  opacity: 0,
  duration: 0.6,
}, '-=2'); // ← overlaps too much!
After (fixed)
// Keep overlap smaller than
// the previous tween's duration
tl.from('#tl5a', {
  opacity: 0,
  duration: 0.4,
})
.from('#tl5b', {
  opacity: 0,
  duration: 0.6,
}, '-=0.2'); // ← safe overlap

Why it happens: GSAP positions tweens on a timeline using real time values. If '-=2' would place a tween before t=0, GSAP clips it. The tween starts at 0 but has already "missed" its beginning, so it may appear to skip or not play at all. Keep overlaps smaller than the previous tween duration.