Frontend Development 13 min read

Optimizing Infinite‑Scroll Waterfall Layouts with CSS Grid, IntersectionObserver, and Modern Browser Features

This article explains how to build a high‑performance infinite‑scroll waterfall layout for e‑commerce using CSS Grid, IntersectionObserver, native lazy‑loading attributes, asynchronous decoding, React's useTransition, content‑visibility, and the AVIF image format to reduce load time and improve smoothness.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Optimizing Infinite‑Scroll Waterfall Layouts with CSS Grid, IntersectionObserver, and Modern Browser Features

Infinite‑scroll waterfall layouts are one of the most common and critical components in e‑commerce sites, so having a high‑performance implementation is essential.

Using Grid Layout to Win the Starting Line

CSS Grid makes responsive layouts simple, requires only pure CSS, and does not depend on JavaScript, giving a performance advantage from the start.

<style>
  .container {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 12px;
    padding: 12px;
  }
</style>

<div class="container">
  <div class="card">
    <img src="https://via.placeholder.com/300" alt="商品1">
    <h2>商品名称 1</h2>
    <p>商品描述 1</p>
  </div>
  ...
</div>

Implementing Lazy Loading with IntersectionObserver

Traditional lazy loading listens to scroll events to detect when an element approaches the viewport, which can be costly. Modern browsers provide the native IntersectionObserver API to monitor element visibility without frequent scroll listeners, reducing CPU and memory usage.

const target = document.getElementById('targetElement');

// Create an observer
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // Element entered viewport
      target.style.backgroundColor = 'lightgreen';
      console.log('元素已进入 Viewport!');
    } else {
      // Element left viewport
      target.style.backgroundColor = 'lightblue';
      console.log('元素已离开 Viewport!');
    }
  });
});

// Start observing
observer.observe(target);

We can improve the waterfall component by integrating this observer.

import React, { useEffect, useRef, useState } from 'react';

const Waterfall = () => {
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(false);
  const observerRef = useRef();

  const fetchItems = async () => {
    setLoading(true);
    const newItems = Array.from({ length: 10 }, (_, index) => ({
      src: 'https://via.placeholder.com/300x300',
    }));
    setTimeout(() => {
      setItems(prev => [...prev, ...newItems]);
      setLoading(false);
    }, 1000);
  };

  useEffect(() => {
    const loadMore = (entries) => {
      if (entries[0].isIntersecting) {
        fetchItems();
        observerRef.current.disconnect();
      }
    };
    const observer = new IntersectionObserver(loadMore, { rootMargin: '1000px' });
    const target = document.querySelector('#load-more');
    if (target) observer.observe(target);
    return () => observer.disconnect();
  }, [items]);

  useEffect(() => { fetchItems(); }, []);

  return (
{items.map(item => (
))}
      {loading &&
加载中...
}
);
};

export default Waterfall;

Native Image Lazy Loading loading="lazy"

Beyond JavaScript lazy loading, most modern browsers support the native loading="lazy" attribute, which lets the browser decide the optimal loading time based on heuristics such as proximity to the viewport, network conditions, CPU/memory usage, and battery state.

<img src="image-to-lazy-load.jpg" loading="lazy">

Asynchronous Decoding of Non‑First‑Screen Images

Decoding images and videos is CPU‑intensive. Adding decoding="async" allows the browser to decode media in a background thread, preventing main‑thread blockage and improving perceived performance.

<img src="image.jpg" decoding="async">

Using useTransition for Smooth Scrolling

When the waterfall continuously loads items during scrolling, React may perform many renders, causing jank. React 18’s concurrent mode lets developers mark state updates as interruptible with useTransition , allowing non‑blocking UI updates.

import React, { useState, useTransition } from 'react';

const ExampleComponent = () => {
  const [items, setItems] = useState([]);
  const [isPending, startTransition] = useTransition();

  const addItem = () => {
    startTransition(() => {
      setItems(prev => [...prev, `Item ${prev.length + 1}`]);
    });
  };

  return (
{isPending ? 'Adding...' : 'Add Item'}
{items.map((item, i) => (
{item}
))}
);
};

Applying useTransition to the waterfall delays rendering of newly fetched items, keeping scrolling fluid.

// src/Waterfall.js
import React, { useEffect, useRef, useState, useTransition } from 'react';
import './App.css';

const fetchItems = (count) => new Promise(resolve => {
  setTimeout(() => {
    resolve(Array.from({ length: count }, (_, i) => {
      const height = Math.floor(Math.random() * (300 - 100 + 1)) + 100;
      return { height, src: `https://via.placeholder.com/200x${height}` };
    }));
  }, 1000);
});

const Waterfall = () => {
  const [items, setItems] = useState([]);
  const [isPending, startTransition] = useTransition();
  const observerRef = useRef();

  const loadItems = () => {
    startTransition(() => {
      fetchItems(10).then(newItems => setItems(prev => [...prev, ...newItems]));
    });
  };

  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        loadItems();
        observer.disconnect();
      }
    }, { rootMargin: '1000px' });
    if (observerRef.current) observer.observe(observerRef.current);
    return () => observer.disconnect();
  }, [items]);

  useEffect(() => { loadItems(); }, []);

  return (
{items.map((item, i) => (
商品名称 {i + 1}
商品描述 {i + 1}
))}
      {isPending &&
加载中...
}
);
};

export default Waterfall;

Delaying Rendering of Off‑Viewport Elements with content-visibility

The CSS content-visibility property lets the browser skip rendering of elements that are not currently visible, reducing initial load time and memory usage. Paired with contain-intrinsic-size , it provides a placeholder size before the element is rendered.

<style>
  .image-gallery {
    content-visibility: auto;
    contain-intrinsic-size: 340px 340px; /* placeholder size */
  }
</style>

<div class="image-gallery">
  <img src="image1.jpg" alt="描述1">
  <img src="image2.jpg" alt="描述2">
</div>

In the demo, the CSS can be refined as follows:

.waterfall-container {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 10px;
  padding: 10px;
}

.item {
  content-visibility: auto;
  padding: 10px;
  background-color: #f2f3f7;
  border-radius: 5px;
}

.item img { width: 100%; }

Higher‑Compression Image Format AVIF

AVIF, based on the AV1 video codec, offers significantly higher compression than JPEG or WebP while preserving detail—about 50% smaller than JPEG and 20% smaller than WebP. Major browsers support AVIF, and servers can negotiate the format via the Accept header.

Relying on front‑end format detection (e.g., loading a 1 px test image) introduces latency and extra code changes; using server‑side content negotiation avoids these drawbacks.

Conclusion

Performance optimizations for waterfall layouts include:

Pure CSS Grid for responsive layout.

Native IntersectionObserver for lazy loading.

Native loading="lazy" attribute for images.

Asynchronous image decoding with decoding="async" .

React useTransition for non‑blocking renders.

content-visibility to skip off‑screen rendering.

Using the high‑compression AVIF image format.

With these techniques, the waterfall layout becomes truly lazy and performant.

performanceCSS GridIntersectionObserverlazy-loadingAVIFcontent-visibility
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.