← Controllers

animate

Scroll-triggered animations using IntersectionObserver.

Usage

<div data-controller="animate"
     data-animate-class-value="animate-fade-in">
  Content that animates when scrolled into view
</div>

<!-- With delay for staggered animations -->
<div data-controller="animate"
     data-animate-class-value="animate-fade-in"
     data-animate-delay-value="200">
  Delayed animation
</div>

Values

Value Type Default Description
class String "animate-in" CSS class to add when visible
delay Number 0 Delay in milliseconds
threshold Number 0.1 Visibility threshold (0-1)

CSS Animation Classes

/* Initial state - invisible */
[data-controller="animate"] {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}

/* Animated state */
.animate-fade-in {
  opacity: 1;
  transform: translateY(0);
}

Source

import { Controller } from "@hotwired/stimulus";

export default class extends Controller {
  static values = {
    class: { type: String, default: "animate-in" },
    delay: { type: Number, default: 0 },
    threshold: { type: Number, default: 0.1 },
  };

  connect() {
    this.observer = new IntersectionObserver(
      (entries) => this.handleIntersect(entries),
      { threshold: this.thresholdValue }
    );
    this.observer.observe(this.element);
  }

  disconnect() {
    this.observer.disconnect();
  }

  handleIntersect(entries) {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        setTimeout(() => {
          this.element.classList.add(this.classValue);
        }, this.delayValue);
        this.observer.unobserve(this.element);
      }
    });
  }
}

Staggered Animations

Use increasing delay values for a staggered effect:

<div class="grid grid-cols-3 gap-4">
  <div data-controller="animate" data-animate-delay-value="0">Card 1</div>
  <div data-controller="animate" data-animate-delay-value="100">Card 2</div>
  <div data-controller="animate" data-animate-delay-value="200">Card 3</div>
</div>