FasterMotion
API ReferenceCore

Tween - Triggers & Scroll

Interactive triggers and scroll-linked animations

Make animations respond to user interactions and scroll events with powerful trigger modes and scroll integration.

Trigger Modes

Control when animations play with built-in trigger modes.

hover

Type: 'hover'

Play animation on hover, reverse on hover end.

FasterMotion.dom({
  target: '.button',
  to: { scale: 1.1 },
  duration: 300,
  trigger: 'hover'
});

Try it: Hover Trigger

FasterMotion.dom({
  target: '.button',
  to: { scale: 1.1 },
  duration: 300,
  trigger: 'hover'
});

click

Type: 'click'

Play animation on click.

FasterMotion.dom({
  target: '.button',
  to: { rotation: 360 },
  duration: 500,
  trigger: 'click'
});

Try it: Click Trigger

FasterMotion.dom({
  target: '.button',
  to: { rotation: 360 },
  duration: 500,
  trigger: 'click'
});

visible

Type: 'visible'

Play animation when element enters viewport.

FasterMotion.dom({
  target: '.card',
  from: { opacity: 0, y: 50 },
  to: { opacity: 1, y: 0 },
  duration: 800,
  trigger: 'visible'
});

Try it: Visible Trigger

FasterMotion.dom({
  target: '.card',
  from: { opacity: 0, y: 50 },
  to: { opacity: 1, y: 0 },
  duration: 800,
  trigger: 'visible'
});

manual

Type: 'manual' | Default

Animation starts immediately (default behavior).

FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  duration: 1000,
  trigger: 'manual'  // Optional, this is default
});

Scroll Animations

Animate based on scroll position.

scroll

Type: 'scroll'

Link animation progress to scroll position.

FasterMotion.dom({
  target: '#parallax',
  to: { y: 200 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'top bottom',    // When element top hits viewport bottom
    end: 'bottom top'       // When element bottom hits viewport top
  }
});

scrollOptions

Type: object

Configure scroll-linked animation behavior.

FasterMotion.dom({
  target: '#header',
  to: { opacity: 0, y: -100 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'top top',       // Animation starts when element top hits viewport top
    end: 'bottom top',      // Animation ends when element bottom hits viewport top
    scrub: true             // Smooth scrubbing
  }
});

start

Type: string | Default: 'top bottom'

When animation begins. Format: 'element viewport'

Element positions:

  • 'top' - Top of element
  • 'center' - Center of element
  • 'bottom' - Bottom of element
  • Offset: 'top+100px', 'center-50px'

Viewport positions:

  • 'top' - Top of viewport
  • 'center' - Center of viewport
  • 'bottom' - Bottom of viewport
// Start when element center hits viewport center
FasterMotion.dom({
  target: '#box',
  to: { scale: 2 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'center center'
  }
});

end

Type: string | Default: 'bottom top'

When animation completes. Same format as start.

// End when element top hits viewport top
FasterMotion.dom({
  target: '#box',
  to: { opacity: 0 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'top bottom',
    end: 'top top'
  }
});

scrub

Type: boolean | number | Default: false

Smooth scrolling interpolation.

// Instant scrubbing
FasterMotion.dom({
  target: '#box',
  to: { rotation: 360 },
  trigger: 'scroll',
  scrollOptions: {
    scrub: true
  }
});

// Smooth with 0.5s delay
FasterMotion.dom({
  target: '#box',
  to: { rotation: 360 },
  trigger: 'scroll',
  scrollOptions: {
    scrub: 0.5
  }
});

Common Patterns

Hover Scale Button

FasterMotion.dom({
  target: '.cta-button',
  to: { scale: 1.05 },
  duration: 200,
  trigger: 'hover',
  ease: 'curve2.out'
});

Try it: Hover Scale Button

FasterMotion.dom({
  target: '.cta-button',
  to: { scale: 1.05 },
  duration: 200,
  trigger: 'hover',
  ease: 'curve2.out'
});

Click Burst Effect

FasterMotion.dom({
  target: '.like-button',
  to: { scale: 1.3 },
  duration: 300,
  trigger: 'click',
  yoyo: true,
  ease: 'elastic.out'
});

Try it: Click Burst Effect

FasterMotion.dom({
  target: '.like-button',
  to: { scale: 1.3 },
  duration: 300,
  trigger: 'click',
  yoyo: true,
  ease: 'elastic.out'
});

Fade In On Scroll

FasterMotion.dom({
  target: '.feature-card',
  from: { opacity: 0, y: 30 },
  to: { opacity: 1, y: 0 },
  duration: 600,
  trigger: 'visible',
  ease: 'curve2.out'
});

Try it: Fade In on Scroll

FasterMotion.dom({
  target: '.feature-card',
  from: { opacity: 0, y: 30 },
  to: { opacity: 1, y: 0 },
  duration: 600,
  trigger: 'visible',
  ease: 'curve2.out'
});

Parallax Background

FasterMotion.dom({
  target: '.parallax-bg',
  to: { y: -200 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'top bottom',
    end: 'bottom top',
    scrub: true
  }
});

Try it: Parallax Background

FasterMotion.dom({
  target: '.parallax-bg',
  to: { y: -200 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'top bottom',
    end: 'bottom top',
    scrub: true
  }
});

Scroll Progress Bar

FasterMotion.dom({
  target: '.progress-bar',
  to: { scaleX: 1 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'top top',
    end: 'bottom bottom',
    scrub: true
  }
});

Try it: Scroll Progress Bar

FasterMotion.dom({
  target: '.progress-bar',
  to: { scaleX: 1 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'top top',
    end: 'bottom bottom',
    scrub: true
  }
});

Sticky Header Fade

FasterMotion.dom({
  target: '.header',
  to: { opacity: 0.8, y: -10 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'top top',
    end: 'top+=100px top',
    scrub: true
  }
});

Image Reveal on View

FasterMotion.dom({
  target: '.hero-image',
  from: { scale: 1.2, opacity: 0 },
  to: { scale: 1, opacity: 1 },
  duration: 1000,
  trigger: 'visible',
  ease: 'curve2.out'
});

Advanced Scroll Patterns

Pin and Animate

// Animate while element is pinned
FasterMotion.dom({
  target: '.pinned-section',
  to: { rotation: 360 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'top top',
    end: 'bottom top',
    scrub: true,
    pin: true
  }
});

Multi-Stage Scroll

// First stage: fade in
FasterMotion.dom({
  target: '.content',
  from: { opacity: 0 },
  to: { opacity: 1 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'top bottom',
    end: 'top center',
    scrub: true
  }
});

// Second stage: slide up
FasterMotion.dom({
  target: '.content',
  to: { y: -100 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'center center',
    end: 'bottom top',
    scrub: true
  }
});

Horizontal Scroll Section

FasterMotion.dom({
  target: '.horizontal-container',
  to: { x: -1000 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'top top',
    end: 'bottom bottom',
    scrub: true,
    pin: true
  }
});

Text Reveal on Scroll

// Stagger text lines
const lines = document.querySelectorAll('.text-line');

lines.forEach((line, i) => {
  FasterMotion.dom({
    target: line,
    from: { opacity: 0, y: 20 },
    to: { opacity: 1, y: 0 },
    duration: 600,
    delay: i * 100,
    trigger: 'visible',
    ease: 'curve2.out'
  });
});

Zoom on Scroll

FasterMotion.dom({
  target: '.zoom-image',
  from: { scale: 1 },
  to: { scale: 1.5 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'top bottom',
    end: 'center center',
    scrub: true
  }
});

Try it: Zoom on Scroll

FasterMotion.dom({
  target: '.zoom-image',
  from: { scale: 1 },
  to: { scale: 1.5 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'top bottom',
    end: 'center center',
    scrub: true
  }
});

Trigger Combinations

Hover + Click

// Hover effect
const hoverAnim = FasterMotion.dom({
  target: '.interactive-card',
  to: { y: -5, boxShadow: '0 10px 30px rgba(0,0,0,0.2)' },
  duration: 300,
  trigger: 'hover'
});

// Click effect
const clickAnim = FasterMotion.dom({
  target: '.interactive-card',
  to: { scale: 0.95 },
  duration: 150,
  trigger: 'click',
  yoyo: true
});

Scroll + Visible

// Trigger on visible
FasterMotion.dom({
  target: '.section',
  from: { opacity: 0 },
  to: { opacity: 1 },
  duration: 600,
  trigger: 'visible'
});

// Then link to scroll
setTimeout(() => {
  FasterMotion.dom({
    target: '.section .content',
    to: { y: -50 },
    trigger: 'scroll',
    scrollOptions: {
      start: 'top bottom',
      end: 'bottom top',
      scrub: true
    }
  });
}, 650);

TypeScript Support

import { FasterMotion } from 'faster-motion';

// Typed trigger modes
FasterMotion.dom({
  target: '#box',
  to: { scale: 1.2 },
  duration: 300,
  trigger: 'hover' as const
});

// Typed scroll options
FasterMotion.dom({
  target: '#parallax',
  to: { y: 200 },
  trigger: 'scroll' as const,
  scrollOptions: {
    start: 'top bottom',
    end: 'bottom top',
    scrub: true
  }
});

Performance Tips

Debounce Scroll Events

// Use scrub for smooth performance
FasterMotion.dom({
  target: '#box',
  to: { rotation: 360 },
  trigger: 'scroll',
  scrollOptions: {
    scrub: 0.3  // Smooth with 300ms interpolation
  }
});

Limit Scroll Range

// Animate only when in view
FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  trigger: 'scroll',
  scrollOptions: {
    start: 'top bottom',
    end: 'bottom top'  // Limited range
  }
});

Use will-change

.scroll-animated {
  will-change: transform;
}

Common Mistakes

Avoid

// Multiple conflicting triggers
FasterMotion.dom({
  target: '#box',
  to: { x: 100 },
  trigger: 'hover'
});
FasterMotion.dom({
  target: '#box',
  to: { x: 200 },
  trigger: 'click'
}); // Conflicts!

// Scroll without scrub for smooth animations
FasterMotion.dom({
  target: '#box',
  to: { y: 100 },
  trigger: 'scroll'
  // Missing scrub: true
});

Better

// Manage state for multiple interactions
let currentTrigger = 'idle';

const triggers = {
  hover: () => {
    if (currentTrigger !== 'click') {
      FasterMotion.dom({ target: '#box', to: { x: 100 } });
      currentTrigger = 'hover';
    }
  },
  click: () => {
    FasterMotion.dom({ target: '#box', to: { x: 200 } });
    currentTrigger = 'click';
  }
};

// Always use scrub for scroll
FasterMotion.dom({
  target: '#box',
  to: { y: 100 },
  trigger: 'scroll',
  scrollOptions: { scrub: true }
});

See Also

On this page