“Nếu bạn đã từng lướt Facebook, Instagram hay TikTok hàng giờ liền mà không thấy điểm dừng, thì bạn đã thấy được hiệu quả giữ chân người dùng của kỹ thuật Infinite Scroll (Cuộn vô tận).”
Trong thế giới Front-End hiện đại, việc hiển thị một danh sách dữ liệu khổng lồ (hàng nghìn, thậm chí hàng triệu bản ghi) mà không làm “treo” trình duyệt là một bài toán kinh điển. Thay vì sử dụng Pagination (phân trang) cổ điển kiểu “Trang 1, 2, 3…”, Infinite Scroll mang lại trải nghiệm mượt mà và liền mạch hơn hẳn.

1. Infinite Scroll là gì? Sự khác biệt với Pagination
Hiểu đơn giản, Infinite Scroll là kỹ thuật tự động tải thêm nội dung mới và chèn vào cuối trang khi người dùng cuộn xuống gần đáy.
Thay vì tải toàn bộ 1000 sản phẩm cùng lúc (khiến trang web tải siêu chậm – High Latency), chúng ta chia nhỏ dữ liệu thành các “chunks” hoặc “pages” (ví dụ: mỗi lần tải 20 sản phẩm).
| Đặc điểm | Pagination (Phân trang) | Infinite Scroll (Cuộn vô tận) |
| Cơ chế | Người dùng click trang 1, 2, 3… | Người dùng cuộn, dữ liệu tự hiện. |
| Mục đích | Tìm kiếm thông tin cụ thể, định hướng đích đến. | Khám phá nội dung (Discovery), giải trí. |
| Tải trang | Tải lại trang (hoặc một phần) khi chuyển trang. | Tải âm thầm (Ajax/Fetch) và nối đuôi (Append). |
| Ví dụ | Google Search, Shopee (kết quả tìm kiếm), Admin Dashboard. | Facebook Newsfeed, Pinterest, Twitter. |
2. Tại sao lại cần Infinite Scroll? (Góc độ Optimization & UX)
Là một Developer, chúng ta quan tâm đến sự cân bằng giữa Performance (Hiệu năng) và UX (Trải nghiệm người dùng).
Về Performance (Hiệu năng kỹ thuật)
- Giảm Time to Interactive (TTI): Trình duyệt chỉ cần parse và render một lượng nhỏ DOM ban đầu (ví dụ 10 item). Trang web hiển thị gần như tức thì.
- Tiết kiệm băng thông server: Người dùng không cuộn xuống thì không tải thêm. Nếu họ chỉ xem 5 bài đầu rồi thoát, bạn không tốn tài nguyên gửi 995 bài còn lại.
Về UX (Trải nghiệm người dùng)
- Flow trải nghiệm liên tục (Frictionless): Loại bỏ thao tác click nút “Next” – một rào cản tâm lý khiến người dùng dễ rời đi.
- Tối ưu cho Mobile: Trên màn hình cảm ứng, thao tác vuốt (swipe) tự nhiên và dễ chịu hơn gấp nhiều lần việc cố bấm vào con số bé tí của thanh phân trang.
3. Cơ chế hoạt động: Từ “Scroll Event” đến “Intersection Observer”
Đây là phần “thịt” của bài toán kỹ thuật.
Cách cũ: window.onscroll (Cơn ác mộng hiệu năng)
Trước đây, chúng ta thường lắng nghe sự kiện cuộn:
window.addEventListener('scroll', () => {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
// Load more...
}
});
Vấn đề: Sự kiện scroll bắn ra liên tục (hàng trăm lần mỗi giây). Tính toán lại chiều cao trang liên tục sẽ gây ra hiện tượng Layout Thrashing (tính toán lại bố cục liên tục), làm tụt FPS nghiêm trọng. Dù có dùng debounce hay throttle, đây vẫn không phải là giải pháp tối ưu nhất cho trình duyệt hiện đại.
Giải pháp hiện đại: Intersection Observer API 🚀
Đây là công nghệ “chuẩn chỉ” được trình duyệt hỗ trợ (Native API), cho phép chúng ta theo dõi một phần tử xem nó có “giao nhau” (intersect) với khung nhìn (viewport) hay không. Nó chạy bất đồng bộ (asynchronous) và không chặn Main Thread.
Quy trình chuẩn (The Sentinel Strategy):
- Hiển thị danh sách dữ liệu hiện tại.
- Đặt một phần tử rỗng (hoặc loading spinner) ở cuối danh sách. Phần tử này gọi là Sentinel (Lính canh).
- Dùng
IntersectionObservertheo dõi Sentinel. - Khi Sentinel lọt vào màn hình (người dùng cuộn tới đáy) ➔ Kích hoạt callback gọi API.
- Dữ liệu mới về ➔ Render vào DOM (chèn lên trên Sentinel) ➔ Sentinel bị đẩy xuống dưới cùng mới.
- Lặp lại quy trình.
Ví dụ triển khai với React (Custom Hook)
Dưới đây là một ví dụ thực tế về cách bạn có thể viết một Custom Hook tái sử dụng:
import { useEffect, useRef } from 'react';
export const useInfiniteScroll = (callback, isFetching) => {
const observerRef = useRef(null); // Ref cho Sentinel
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
// Nếu Sentinel xuất hiện VÀ không đang fetch dữ liệu
if (entries[0].isIntersecting && !isFetching) {
callback();
}
},
{
root: null, // null nghĩa là viewport
rootMargin: '200px', // Gọi API trước khi user cuộn tới đáy 200px (tải trước)
threshold: 0.1, // Chỉ cần thấy 10% Sentinel là kích hoạt
}
);
if (observerRef.current) {
observer.observe(observerRef.current);
}
return () => {
if (observerRef.current) observer.disconnect();
};
}, [callback, isFetching]);
return observerRef;
};
4. Những cạm bẫy “chết người” và cách hóa giải (Advanced Best Practices)
Infinite Scroll giống như con dao hai lưỡi. Nếu làm tốt, nó rất mượt. Nếu làm ẩu, nó sẽ khiến trình duyệt của người dùng “bốc hơi”.
4.1. DOM Explosion (Bùng nổ DOM) – Vấn đề lớn nhất
Nếu người dùng cuộn qua 100 trang, bạn sẽ có khoảng 2000-3000 node DOM (div, img, li…) trên trang. Điều này khiến trình duyệt ngốn RAM khủng khiếp và làm chậm quá trình style recalculation.
- Giải pháp: Virtualization (Windowing). Kỹ thuật này chỉ render những gì mắt người dùng đang thấy trong Viewport. Các phần tử bị cuộn khuất sẽ bị hủy khỏi DOM và thay thế bằng một khoảng trắng giữ chỗ (placeholder) có chiều cao tương ứng.
- Thư viện khuyên dùng:
react-window,react-virtualized, hoặctanstack-virtual(Headless UI, rất mạnh).
- Thư viện khuyên dùng:
4.2. Scroll Restoration (Khôi phục vị trí cuộn)
Thử tưởng tượng: Bạn lướt Facebook được 30 phút, bấm vào xem chi tiết một bài viết, sau đó bấm nút “Back” của trình duyệt. Bùm! Trang web load lại từ đầu và bạn mất dấu vị trí cũ. Đây là trải nghiệm UX cực tệ.
- Giải pháp:
- Sử dụng các thư viện quản lý state server như TanStack Query (React Query) với tính năng
infiniteQuery. Nó cache dữ liệu rất tốt. - Lưu vị trí cuộn (Scroll Position) vào
sessionStoragehoặc Global State trước khi unmount component và khôi phục lại khi mount.
- Sử dụng các thư viện quản lý state server như TanStack Query (React Query) với tính năng
4.3. Layout Shift (Giật khung hình) & CLS
Khi dữ liệu mới load về, nếu ảnh chưa kịp tải hoặc text có độ dài khác nhau, giao diện sẽ bị đẩy nhảy loạn xạ (Layout Shift).
- Giải pháp:
- Luôn set
widthvàheightcố định cho thẻimghoặc container chứa ảnh. - Sử dụng Skeleton Loading (Khung xương) ở vị trí của Sentinel trong lúc chờ API. Điều này báo hiệu cho người dùng biết “Sắp có dữ liệu, cứ bình tĩnh”.
- Luôn set
4.4. “Footer” không bao giờ với tới
Đây là lỗi thiết kế kinh điển. Bạn để thông tin liên hệ, bản quyền ở Footer. Người dùng cố cuộn xuống để xem, nhưng dữ liệu mới lại chèn vào, đẩy Footer xuống tiếp. Trò chơi đuổi bắt không hồi kết!
- Giải pháp:
- Nếu dùng Infinite Scroll, hãy bỏ Footer truyền thống. Đưa các link quan trọng (About, Contact, Terms) vào Sidebar hoặc Header menu.
- Hoặc sử dụng nút “Load More” khi danh sách đã quá dài (ví dụ sau khi tự động load 3 lần thì yêu cầu bấm nút).
5. Khi nào KHÔNG nên dùng Infinite Scroll?
Đừng cuồng kỹ thuật này. Hãy quay lại dùng Pagination truyền thống nếu:
- Mục tiêu là tìm kiếm (Goal-oriented): Người dùng cần tìm lại một sản phẩm cụ thể (“Cái áo đó nằm ở trang 5”). Với Infinite Scroll, việc tìm lại một item đã lướt qua là cực khó.
- Ứng dụng quản trị (Admin Dashboard): Nơi cần quản lý dữ liệu chính xác, check từng dòng.
- Yêu cầu SEO khắt khe: Dù Google Bot hiện tại đã render được JavaScript (Hydration), nhưng việc crawl nội dung nằm sâu ở trang 10 của Infinite Scroll vẫn là một thách thức lớn và không đảm bảo toàn vẹn nội dung được index.
Kết luận
Infinite Scroll là một kỹ thuật mạnh mẽ để giữ chân người dùng và tạo ra trải nghiệm lướt web hiện đại (“Addictive UX”). Tuy nhiên, để làm chủ nó, bạn không chỉ cần biết cách gọi API, mà còn phải biết cách quản lý bộ nhớ (Virtualization), xử lý sự kiện thông minh (Intersection Observer) và chăm chút từng chút một cho trải nghiệm người dùng (Skeleton, Scroll Restoration).
