Rreact.wiki
React Snippets

Fade-in animation with requestAnimationFrame

AnimationanimationperformanceuseEffectrequestAnimationFrameCSS-in-JS

A performant, CSS-in-JS fade-in animation using useState, useEffect, and requestAnimationFrame for smooth entrance transitions.

TSX
import { useState, useEffect, useRef } from 'react';
 
export function FadeIn({ children }: { children: React.ReactNode }) {
  const [isVisible, setIsVisible] = useState(false);
  const ref = useRef<HTMLDivElement>(null);
 
  useEffect(() => {
    const node = ref.current;
    if (!node) return;
 
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true);
          observer.disconnect();
        }
      },
      { threshold: 0.1 }
    );
 
    observer.observe(node);
    return () => observer.disconnect();
  }, []);
 
  useEffect(() => {
    if (!isVisible) return;
 
    let start: number | null = null;
    const duration = 300; // ms
 
    const animate = (timestamp: number) => {
      if (!start) start = timestamp;
      const progress = Math.min((timestamp - start) / duration, 1);
      const opacity = progress;
 
      node.style.opacity = String(opacity);
      node.style.transform = `translateY(${Math.max(0, (1 - progress) * 20)}px)`;
 
      if (progress < 1) {
        requestAnimationFrame(animate);
      }
    };
 
    const node = ref.current;
    if (node) {
      node.style.transition = 'none'; // Disable CSS transition to avoid conflict
      node.style.opacity = '0';
      node.style.transform = 'translateY(20px)';
      requestAnimationFrame(animate);
    }
  }, [isVisible]);
 
  return (
    <div ref={ref} style={{ willChange: 'opacity, transform' }}>
      {children}
    </div>
  );
}

This component fades in content smoothly when it enters the viewport, using requestAnimationFrame for jank-free rendering and IntersectionObserver for efficient visibility detection. It avoids layout thrashing by setting will-change and disabling conflicting CSS transitions. To use, wrap any JSX with <FadeIn>...</FadeIn>. Note: The animation is triggered only once on mount/intersection — no re-triggering on re-renders. For multiple elements, use separate instances or extend with a key-based approach.

Fade-in animation with requestAnimationFrame — react.wiki