Using Intersection Observer for Lazy Loading and Scroll-Based Animations

By : manish

Language: javascript

Date Published: 4 days, 3 hours ago

The Intersection Observer API lets you efficiently detect when an element enters or leaves the viewport without costly scroll events. This is ideal for lazy‑loading images, triggering animations, or loading infinite‑scroll content. By observing a target element, you get a callback with its intersection ratio, letting you decide when to act. For lazy loading, swap a placeholder’s src with the real image once the element is about to enter the viewport. For animations, add a class that starts a CSS animation when the element becomes visible. The API works in all modern browsers and can be polyfilled for older ones. Use a rootMargin to start loading slightly before the element is fully visible, improving perceived performance. Remember to disconnect observers when components unmount to avoid memory leaks. This pattern reduces initial page weight, improves Core Web Vitals (especially LCP), and keeps interactions smooth without jank from scroll‑based throttling.


 

Style : Dark Mode : Line Number: Download Font Size:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// hooks/useIntersection.js
import { useEffect, useState } from "react";

function useIntersectionObserver(options = {}) {
  const [entry, setEntry] = useState(null);
  const [node, setNode] = useState(null);

  useEffect(() => {
    if (!node) return;

    const observer = new IntersectionObserver(([obsEntry]) => {
      setEntry(obsEntry);
    }, options);

    observer.observe(node);
    return () => observer.disconnect();
  }, [node, options]);

  return [setNode, entry];
}

// ImageLazy.jsx – lazy‑load an image when it’s near the viewport
import { useIntersectionObserver } from "./hooks/useIntersection";

export default function ImageLazy({ src, alt, placeholder = "/placeholder.png" }) {
  const [setRef, entry] = useIntersectionObserver({
    rootMargin: "200px", // start loading 200px before it’s visible
    threshold: 0,
  });

  const [loaded, setLoaded] = useState(false);

  // When the image starts to intersect, load the real src
  useEffect(() => {
    if (entry && entry.isIntersecting) {
      setLoaded(true);
    }
  }, [entry]);

  return (
    <div>
      <img
        ref={setRef}
        src={loaded ? src : placeholder}
        alt={alt}
        style={{ display: "block", maxWidth: "100%", height: "auto" }}
        loading="lazy"
      />
      {!loaded && (
        <div
          style={{
            width: "100%",
            paddingTop: "56.25%", // 16:9 placeholder
            background: "#eee",
          }}
        />
      )}
    </div>
  );
}

// FadeInOnScroll.jsx – animate elements as they enter the viewport
import { useIntersectionObserver } from "./hooks/useIntersection";

export default function FadeInOnScroll({ children }) {
  const [setRef, entry] = useIntersectionObserver({
    threshold: 0.1, // trigger when 10% visible
  });

  const [visible, setVisible] = useState(false);

  useEffect(() => {
    if (entry && entry.isIntersecting) {
      setVisible(true);
    }
  }, [entry]);

  return (
    <div ref={setRef} className={visible ? "fade-in" : ""}>
      {children}
    </div>
  );
}

// CSS for the animation (add to your stylesheet)
/*
.fade-in {
  opacity: 0;
  transform: translateY(20px);
  animation: fadeInUp 0.6s ease-out forwards;
}
@keyframes fadeInUp {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
*/
intersection-observer lazy-loading scroll-animations frontend performance
Login to see the comments