Group APIsJune 2026 · 7 min read

Animate multiple elements
on scroll — one call.

Every real landing page needs more than one animated element. svg-scroll-draw ships four group APIs for fan-out, cascade, and parallax — all in svg-scroll-draw/group.

1. The problem with animating many elements

When you have a feature grid, testimonials row, or pricing table, you end up writing the same scrollAnimate call for every element:

before.js
// Before group APIs — repetitive and brittle
scrollAnimate(card1, { props: { opacity: [0, 1] }, easing: 'ease-out', once: true });
scrollAnimate(card2, { props: { opacity: [0, 1] }, easing: 'ease-out', once: true });
scrollAnimate(card3, { props: { opacity: [0, 1] }, easing: 'ease-out', once: true });

// And you have to track all three instances for cleanup:
// inst1.destroy(); inst2.destroy(); inst3.destroy();

The group APIs collapse this to one call that returns a single combined instance — destroy() cleans up everything at once.

2. scrollAnimateGroup — fan-out simultaneously

All elements animate at the same time as the section scrolls into view. Each tracks its own scroll position independently, so elements higher on the page reveal slightly before lower ones — natural cascade for free.

feature-grid.js
import { scrollAnimateGroup } from 'svg-scroll-draw/group';

// All cards animate simultaneously — same options, one call
const group = scrollAnimateGroup(
  document.querySelectorAll('.feature-card'),
  {
    props: {
      opacity:   [0, 1],
      transform: ['translateY(40px)', 'translateY(0)'],
    },
    easing: 'ease-out',
    once:   true,
  }
);

// Full instance API on the whole group
group.replay();   // replay all
group.pause();    // pause all
group.destroy();  // cleanup on unmount

React

FeatureGrid.tsx
import { useEffect, useRef } from 'react';
import { scrollAnimateGroup } from 'svg-scroll-draw/group';

export function FeatureGrid({ features }) {
  const containerRef = useRef(null);

  useEffect(() => {
    if (!containerRef.current) return;
    const cards = containerRef.current.querySelectorAll('.card');
    const group = scrollAnimateGroup(Array.from(cards), {
      props: { opacity: [0, 1], transform: ['translateY(32px)', 'translateY(0)'] },
      easing: 'ease-out',
      once:   true,
    });
    return () => group.destroy();
  }, []);

  return (
    <div ref={containerRef} className="grid grid-cols-3 gap-6">
      {features.map(f => (
        <div key={f.id} className="card p-6 rounded-2xl border">
          <h3>{f.title}</h3>
          <p>{f.description}</p>
        </div>
      ))}
    </div>
  );
}
Trigger offsets for free: Because each element computes its own scroll position, a card near the top of the grid will reveal before a card at the bottom — even with identical trigger settings. No manual offset needed.

3. scrollAnimateSequence — cascade one by one

Each element starts only after the previous one reaches 100%. Perfect for step-by-step reveals, numbered lists, or onboarding flows.

steps.js
import { scrollAnimateSequence } from 'svg-scroll-draw/group';

// Step 1 animates fully, then step 2 starts, then step 3
scrollAnimateSequence(
  document.querySelectorAll('.onboarding-step'),
  {
    props: {
      opacity:   [0, 1],
      transform: ['translateX(-24px)', 'translateX(0)'],
    },
    easing:     'ease-out',
    onComplete: () => console.log('a step finished'),
  }
);

Internally each step is forced to once: trueso completing a step and scrolling back doesn't reset it and break the chain.

4. scrollParallaxGroup — multi-layer parallax

Apply the same parallax speed to multiple elements at once. Typical use: floating UI badges, background texture elements, or decorative shapes that all move at the same rate.

hero.js
import { scrollParallaxGroup } from 'svg-scroll-draw/group';

// All three decorative shapes float upward at 30% of scroll speed
scrollParallaxGroup(
  ['#circle-1', '#circle-2', '#dot-cluster'],
  { speed: 0.3 }
);

// Background layer drifts opposite direction
scrollParallaxGroup(
  ['#bg-gradient', '#noise-overlay'],
  { speed: -0.15 }
);

Multi-layer depth effect

Call scrollParallaxGroup multiple times with different speeds for a classic depth illusion:

parallax-layers.js
import { scrollParallaxGroup } from 'svg-scroll-draw/group';

// Far background — moves slowest
scrollParallaxGroup(['.layer-far'],  { speed: 0.1 });

// Mid layer
scrollParallaxGroup(['.layer-mid'],  { speed: 0.25 });

// Near foreground — moves fastest
scrollParallaxGroup(['.layer-near'], { speed: 0.5 });

// Floating UI elements — drift opposite direction
scrollParallaxGroup(['.badge', '.tag', '.pill'], { speed: -0.2 });

5. scrollDrawGroup — SVG paths in sync

The original group API — animate multiple SVG containers simultaneously with scrollDraw. Same pattern: one call, combined instance.

icons.js
import { scrollDrawGroup, scrollDrawSequence } from 'svg-scroll-draw/group';

// Three SVG icons draw at the same time
const group = scrollDrawGroup(
  ['#icon-speed', '#icon-size', '#icon-framework'],
  { easing: 'ease-out', fade: true, once: true }
);

// Or draw them in sequence (speed → size → framework)
const seq = scrollDrawSequence(
  ['#icon-speed', '#icon-size', '#icon-framework'],
  { easing: 'spring', fade: true }
);

6. Real-world patterns

Pricing section — staggered cards + counters

pricing.js
import { scrollAnimateGroup } from 'svg-scroll-draw/group';
import { scrollCounter } from 'svg-scroll-draw';

// Pricing cards fan in
scrollAnimateGroup(document.querySelectorAll('.pricing-card'), {
  props: { opacity: [0, 1], transform: ['translateY(32px)', 'translateY(0)'] },
  easing: 'ease-out',
  once:   true,
});

// Price numbers count up independently
document.querySelectorAll('.price-number').forEach((el, i) => {
  const prices = [9, 29, 79];
  scrollCounter(el, {
    to:     prices[i],
    format: n => '$' + Math.round(n),
    easing: 'ease-out',
    once:   true,
    trigger: { start: `top ${85 - i * 5}%`, end: `top ${50 - i * 5}%` },
  });
});

Hero with parallax depth + text reveal

hero-section.js
import { scrollParallaxGroup } from 'svg-scroll-draw/group';
import { scrollText } from 'svg-scroll-draw/text';

// Background layers drift at different depths
scrollParallaxGroup(['.bg-blur', '.bg-gradient'], { speed: 0.12 });
scrollParallaxGroup(['.hero-badge', '.hero-dot'],  { speed: -0.18 });

// Headline reveals word by word on top
scrollText('#hero-headline', {
  split:   'words',
  stagger: 0.06,
  from:    { opacity: 0, y: 32 },
  once:    true,
});

Feature list — sequential entrance

feature-list.js
import { scrollAnimateSequence } from 'svg-scroll-draw/group';

// Each feature row slides in after the previous one
scrollAnimateSequence(
  document.querySelectorAll('.feature-row'),
  {
    props: {
      opacity:   [0, 1],
      transform: ['translateX(-20px)', 'translateX(0)'],
    },
    easing: 'ease-out',
  }
);

7. API summary

FunctionBehaviourOptions type
scrollAnimateGroupAll elements animate simultaneouslyScrollAnimateOptions
scrollAnimateSequenceEach starts only after the previous completesScrollAnimateOptions
scrollParallaxGroupAll elements parallax at the same speedScrollParallaxOptions
scrollDrawGroupAll SVG containers draw simultaneouslyScrollDrawOptions
scrollDrawSequenceSVG containers draw in strict sequenceScrollDrawOptions

All functions return a combined ScrollDrawInstance with destroy, replay, pause, resume, seek, and getProgress. All are available in svg-scroll-draw/group.

← Back to blogsvg-scroll-draw · MIT · ~9 KB