Skip to content
Documentation/Stagger, scroll and loops

Search documentation

Type to search the documentation.

Guides

Stagger, scroll and loops

Coordinate sibling delays by DOM order, bind motion to scroll position, and repeat a short animation.

Stagger siblings

MoveStaggerDirective is a context provider: it gives each child's moveEnter, moveInView or variant a delay computed from its position in the DOM, so it works with @for without any manual index bookkeeping. Step accepts a number (milliseconds) or a string with a unit.

features.ts
@Component({
  selector: 'app-features',
  imports: [MoveStaggerDirective, MoveEnterDirective],
  template: `
    <ul moveStagger moveStaggerStep="80ms">
      @for (item of items; track item) {
        <li moveEnter="fade-up">{{ item }}</li>
      }
    </ul>
  `,
})
export class Features {
  protected readonly items = ['Fast', 'Accessible', 'Typed'];
}

View source

moveStaggerDirection orders the delays from the first child, the last, or the center.

moveStagger

Delays come from DOM order. The children use moveEnter, so a change recreates the list to replay it.

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
Inputs

Template

<ul moveStagger [moveStaggerStep]="80" moveStaggerDirection="first">
  @for (item of items(); track item.id) {
    <li moveEnter="fade-up">{{ item.label }}</li>
  }
</ul>

moveStagger — expanded preview

The children use moveEnter, which is one-shot, so the playground recreates the list when a setting changes. Open the full stagger playground.

Combine it with moveInView to reveal sections one after another as they scroll into view:

sections.ts
@Component({
  selector: 'app-sections',
  imports: [MoveStaggerDirective, MoveInViewDirective],
  template: `
    <section moveStagger moveStaggerStep="100ms">
      <article moveInView="fade-up">One</article>
      <article moveInView="fade-up">Two</article>
      <article moveInView="fade-up">Three</article>
    </section>
  `,
})
export class Sections {}

View source

Scroll-linked motion

MoveScrollDirective maps scroll progress onto an animation: progress 0 to 1 becomes the timeline from start to end, and the directive exposes progress as a signal. MoveParallaxDirective does the same with an automatically computed translate range.

scroll-hero.ts
@Component({
  selector: 'app-scroll-hero',
  imports: [MoveScrollDirective],
  template: `
    <!-- Duration is always 1000ms linear internally: scroll progress maps 1:1 onto the timeline. -->
    <div [moveScroll]="{ y: [0, 120], opacity: [1, 0.2] }" #hero="moveScroll">
      Progress: {{ hero.progress() }}
    </div>
  `,
})
export class ScrollHero {}

View source

Both read the native scroll position (smoothed with a short interpolation) and fall back to the end state under reduced motion. Point moveScrollContainer or moveParallaxContainer at a selector for an inner scroll container.

Note

Native scrolling is the default everywhere on this site. moveSmoothScroll and SmoothScrollService change how wheel and touch scrolling feel and are experimental; prefer native scrolling.

Loops

MoveLoopDirective repeats a preset or keyframes. Use it for short, meaningful loops (a spinner, a subtle pulse) and keep them aria-hidden when decorative. Loops stop under reduced motion.

spinner.ts
@Component({
  selector: 'app-spinner',
  imports: [MoveLoopDirective],
  template: `<span moveLoop="spin" moveDuration="1s" aria-hidden="true">◐</span>`,
})
export class Spinner {}

View source

Related: moveStagger playground · moveText playground · moveScroll playground · moveParallax playground · moveLoop playground