DESN 378 · Week 7 Preview

Scroll as a Design Material

Three patterns that turn scroll position into a creative tool. You’ll feel each one as you scroll through this page.

How this works

  • Each part teaches one ScrollTrigger pattern with real, copy-ready code.
  • Below each code block is a live demo zone — scroll through it to feel the concept.
  • How It Works boxes break down the mechanics in plain language.
  • Checkpoints and reflections connect each pattern to your Lost in The Scroll project.
  • Parts 1–3 cover: Basic Trigger, Scrub, and Pin + Timeline.

Progress: 0/3 sections • 0%

1 Basic Trigger

Goal: Understand how ScrollTrigger fires an animation when an element enters the viewport.

Context you have: You built GSAP animations in The Pulse (Build 7-2). They all fire on page load. ScrollTrigger makes them wait for the audience.

How It Works: The Threshold

ScrollTrigger watches scroll position. When the top of the trigger element crosses a line on the viewport (like 80% from the top), the animation fires. Think of it as a tripwire — your element walks into it, and the animation goes off.

The start property defines two things: which edge of the trigger element, and where on the viewport. "top 80%" means “when the top of my element reaches 80% down the viewport.”

The Pattern

// Register the plugin (do this once, at the top)
gsap.registerPlugin(ScrollTrigger);

// Animate when element enters viewport
gsap.from('.card', {
  opacity: 0,
  y: 40,
  scale: 0.92,
  duration: 0.6,
  stagger: 0.15,
  ease: 'power2.out',
  scrollTrigger: {
    trigger: '.cards-container',
    start: 'top 80%',    // "trigger-edge viewport-position"
    markers: true         // visual debugging — remove for production
  }
});

Live Demo — scroll to see these cards animate in & out

Design System

Tokens, scales, and reusable decisions that keep a project consistent.

Motion Library

A vocabulary of entrance, exit, and transition animations to draw from.

Interaction Pattern

A repeatable response to user input that feels natural and intentional.

Scroll Narrative

Content that unfolds as the reader moves, turning position into pacing.

What You Just Saw

The cards animate in when you enter the demo zone, then animate out when the demo zone scrolls away. Scroll back and they animate in again. This live demo uses an end plus toggleActions: "play reverse play reverse" so you can feel enter/leave behavior in both directions.

Hit the Reset button to replay the demo from scratch.

Key Config Options

Property What It Does Example
trigger Which element’s position triggers the animation ".cards-container"
start “trigger-edge viewport-position” format "top 80%"
end When the trigger zone ends "bottom 20%"
markers Visual debugging lines (remove before shipping) true
toggleActions What happens on enter / leave / re-enter / re-leave "play none none reverse"

Reflection

Look at your Lost in The Scroll site. Which sections should use basic trigger? Which need something more than “fire once on scroll”?

Auto-saved ✓

2 Scrub

Goal: Link animation progress directly to scroll position. Scroll forward = animate forward. Scroll back = animate back.

Context you have: Basic trigger fires once. But what if you want scroll to be the playhead? That’s scrub.

How It Works: Scroll as Playhead

With scrub: true, the animation’s progress is tied directly to how far you’ve scrolled through the trigger zone. Scroll 50% through? The animation is 50% done. Scroll backward? The animation reverses.

scrub: 1 adds a one-second smoothing lag — the animation catches up to your scroll position over one second instead of instantly. This feels much better than scrub: true for most designs.

Think of it like a slider: your scrollbar is the animation’s progress bar.

The Pattern

// Scroll position drives the animation — forward and back
const tl = gsap.timeline({
  scrollTrigger: {
    trigger: '#scrub-section',
    start: 'top center',
    end: 'bottom center',
    scrub: 1,             // 1-second smoothing lag
    markers: true
  }
});

tl.to('#headline-1', { x: 0, opacity: 1, ease: 'none' })
  .to('#headline-2', { x: 0, opacity: 1, ease: 'none' }, '<0.1')
  .to('#headline-3', { x: 0, opacity: 1, ease: 'none' }, '<0.1');

Live Demo — scroll slowly. Your scroll controls the animation.

scroll progress
SCROLL
DRIVES
MOTION

What You Just Felt

Scroll backward. The headlines retreat. That’s scrub — animation as a two-way street.

Notice the code uses ease: 'none'. With scrub, your scroll speed already provides natural easing. Adding a complex ease on top feels overcooked.

Common Trap

Using complex easing (like bounce.out) with scrub. Since scroll speed already provides easing, ease: 'none' usually feels best for scrubbed animations. Save the fancy easing for basic trigger animations.

Reflection

Which section of your Lost in The Scroll site would benefit from scrub? Think about a section where the content should progress with scroll, not just appear.

Auto-saved ✓

3 Pin + Timeline

Goal: Pin a visual element in place while scroll drives a timeline of reveals inside it. The scrollytelling signature move.

Context you have: In scrollytelling, the most powerful technique is pinning: locking a section while the user scrolls through “steps” that reveal content inside the pinned area. This is the Apple product page pattern, deconstructed.

How It Works: Hold the Stage

pin: true locks an element in place while the rest of the page scrolls past it. GSAP automatically adds extra scroll distance to compensate — the page gets longer, and that extra distance becomes the timeline’s playback range.

Combine pinning with scrub and a timeline, and you get the scrollytelling pattern: a fixed visual stage where content reveals step by step as the reader scrolls.

end: "+=200%" means the pin lasts for 2x the section height in scroll distance. More distance = slower pacing = more room for reveals.

The Pattern

// Pin the visual element; scroll drives the timeline
const pinTl = gsap.timeline({
  scrollTrigger: {
    trigger: '.pin-section',
    start: 'top top',
    end: '+=200%',        // pin lasts for 2x the section height
    pin: true,
    scrub: 1,
    markers: true
  }
});

// Sequenced reveals inside the pinned section
pinTl
  .from('.step-1', { opacity: 0, y: 30, duration: 1 })
  .from('.step-2', { opacity: 0, y: 30, duration: 1 })
  .from('.step-3', { opacity: 0, y: 30, duration: 1 });

Live Demo — the left panel pins while you scroll through the steps

Feature

The Long Read

A deep investigation into how motion design shapes editorial narrative on the modern web.

Read Now →

Step 01 / The Pin

Lock the visual in place.

The left card is pinned with pin: true. It stays in the viewport while you scroll through this column.

Step 02 / The Timeline

Sequence reveals inside the pin.

A GSAP timeline fires as you enter this step — the headline fades up inside the pinned card. Scroll controls when it triggers.

Step 03 / The Payoff

The full composition arrives.

Sub-copy and the CTA complete the card. Three scroll zones, one visual, one timeline. This is the Apple product page pattern — deconstructed.

What You Just Experienced

The left panel stayed fixed while you scrolled through the steps on the right. Each step revealed new content inside the pinned card. When all steps completed, the pin released and the section scrolled away.

The end: "+=200%" controlled how long the pin lasted. More scroll distance = slower, more dramatic reveals.

Pin + Mobile

On small screens, pinning can feel disorienting. Consider disabling the pin on mobile and stacking the layout vertically instead. Use ScrollTrigger.matchMedia() to set different behaviors per breakpoint:

ScrollTrigger.matchMedia({
  "(min-width: 769px)": function() {
    // Desktop: pin + scrub timeline
  },
  "(max-width: 768px)": function() {
    // Mobile: simple stagger, no pin
  }
});

Common Pitfalls

  • Pin spacing: GSAP adds padding-bottom to prevent layout jumps. Don’t fight it — it’s keeping your page length correct.
  • Nested pins: Avoid putting a pinned section inside another pinned section. It creates conflicting scroll calculations.
  • Performance: Pinning is GPU-intensive. Keep pinned animations simple — opacity and transforms only. Avoid animating layout properties like width or height.

Reflection

The showpiece section of your Lost in The Scroll site — could it use pinning? Describe how you would structure the pinned visual and the scroll steps.

Auto-saved ✓

Three Patterns, Side by Side

Every ScrollTrigger interaction is one of these three patterns — or a combination of them.

// ── PATTERN 1: BASIC TRIGGER ──
// Animation fires once when element enters viewport.
// Use for: section entrances, card reveals, hero load-ins.

gsap.registerPlugin(ScrollTrigger);

gsap.from('.card', {
  scrollTrigger: {
    trigger: '.cards-container',   // element to watch
    start: 'top 80%',             // fires here
    // toggleActions: 'play none none reverse'  // optional replay
  },
  opacity: 0,
  y: 40,
  scale: 0.92,
  duration: 0.6,
  stagger: 0.15,
  ease: 'power2.out'
});
// ── PATTERN 2: SCRUB ──
// Animation progress = scroll progress. Forward and back.
// Use for: progress bars, text reveals, parallax-like effects.

const scrubTl = gsap.timeline({
  scrollTrigger: {
    trigger: '#section',
    start: 'top center',          // trigger-edge viewport-edge
    end: 'bottom center',
    scrub: 1                      // 1-second smoothing lag
  }
});

scrubTl
  .to('#el-1', { x: 0, opacity: 1, ease: 'none' })
  .to('#el-2', { x: 0, opacity: 1, ease: 'none' }, '<0.1')   // overlap
  .to('#el-3', { x: 0, opacity: 1, ease: 'none' }, '<0.1');
// ── PATTERN 3: PIN + TIMELINE ──
// Pin an element. Scroll through reveals inside it.
// Use for: scrollytelling showpieces, product features, data stories.

const pinTl = gsap.timeline({
  scrollTrigger: {
    trigger: '.pin-section',
    start: 'top top',
    end: '+=200%',                // pin duration as scroll distance
    pin: true,
    scrub: 1
  }
});

pinTl
  .from('.step-1', { opacity: 0, y: 30, duration: 1 })
  .from('.step-2', { opacity: 0, y: 30, duration: 1 })
  .from('.step-3', { opacity: 0, y: 30, duration: 1 });

You Felt It.

You didn’t just read about ScrollTrigger — you scrolled through it. Three patterns, three different relationships between scroll and motion.

What You Learned

  1. Basic Trigger — animation fires when an element enters the viewport. One crossing, one animation.
  2. Scrub — scroll position controls animation progress. Forward and backward. The scrollbar is the playhead.
  3. Pin + Timeline — section locks in place while scroll drives a sequence of reveals. The scrollytelling signature.
  4. Markers — visual debugging for trigger positions. Use them constantly, remove before shipping.
  5. Reduced motion — always wrap animation code in a prefers-reduced-motion check. Every time.

Connection to Your Project

These patterns map directly to your Lost in The Scroll site:

  • Basic trigger for your section entrances (hero, chapter openings)
  • Scrub for a visual effect section (progress bar, parallax text)
  • Pin + timeline for your showpiece section (the moment that stops the reader)

Next week in class, you’ll take the GSAP animations you built in The Pulse and wrap them in ScrollTrigger configs. The choreography stays the same — you’re just adding curtain cues.

Final Reflection

Which pattern surprised you the most? Which feels most relevant to your Lost in The Scroll site? What questions do you still have heading into class?

Auto-saved ✓

Before You Leave

  1. Click “Copy All My Responses” below.
  2. Paste them into a note or your project repo — you’ll reference these in class.