Cuộn vô hạn (Infinite Scroll) là kỹ thuật tự động tải nội dung khi người dùng chạm đến cuối danh sách hiện tại. Theo UX Design Collective, 2024, Infinite Scroll làm tăng thời gian phiên trong mạng xã hội lên 40–60% so với phân trang. Trong phát triển di động, kỹ thuật này được triển khai thông qua sự kết hợp của scroll listeners và các yêu cầu API với phân trang dựa trên con trỏ. Cuộn vô hạn đã trở thành tiêu chuẩn thực tế cho các nguồn cấp nội dung, nhưng đòi hỏi triển khai cẩn thận để tránh các vấn đề về hiệu suất và điều hướng.
Ý chính
Cuộn vô hạn (Infinite Scroll) là một mô hình tải dữ liệu trong đó các phần tử mới được tự động thêm vào cuối danh sách khi người dùng cuộn. Người dùng không nhấp vào các nút «Tiếp theo» hoặc «Tải thêm» — hệ thống tự xác định khi nào cần yêu cầu lô dữ liệu tiếp theo và chèn các mục mới vào danh sách hiện có một cách liền mạch.
Infinite Scroll trở nên phổ biến nhờ các mạng xã hội — Twitter, Instagram và TikTok sử dụng nó như cơ chế phân phối nội dung chính. Theo Nielsen Norman Group (2024), Cuộn vô hạn làm tăng mức độ tương tác lên 30–50% cho các ứng dụng nội dung vì nó giảm tải nhận thức: người dùng không cần quyết định chuyển đến trang tiếp theo. Tuy nhiên, đối với các tác vụ yêu cầu điều hướng chính xác (tìm kiếm, so sánh sản phẩm), Cuộn vô hạn có thể làm giảm hiệu quả.
Về mặt kỹ thuật, Cuộn vô hạn bao gồm ba thành phần: một scroll listener (theo dõi vị trí cuộn), một threshold (khoảng cách đến cuối danh sách để kích hoạt tải) và một cơ chế phân trang (yêu cầu API và chèn dữ liệu). Việc cấu hình đúng threshold rất quan trọng: nếu bộ kích hoạt quá sớm (1000 px tính từ cuối), người dùng sẽ nhận được các yêu cầu không cần thiết; nếu quá muộn (50 px), người dùng sẽ nhận thấy sự tạm dừng khi tải.
Kiến trúc của Cuộn vô hạn được xây dựng trên mô hình hướng sự kiện: thành phần danh sách tạo một sự kiện khi đạt đến ngưỡng cuộn, ViewModel xử lý sự kiện đó và gọi kho lưu trữ để tải lô dữ liệu tiếp theo. Sau khi nhận được phản hồi, các phần tử mới được chèn vào danh sách và giao diện người dùng được cập nhật thông qua bộ chuyển đổi. Chuỗi này phải không đồng bộ và không được chặn luồng giao diện người dùng.
Thuật toán Cuộn vô hạn cơ bản bao gồm bốn bước. Khởi tạo: khi màn hình được mở lần đầu tiên, lô dữ liệu đầu tiên được tải (trang 1 hoặc cursor = null). Theo dõi: scroll listener kiểm tra xem người dùng đã đạt đến ngưỡng hay chưa — thường là 200–500 px tính từ cuối danh sách. Tải: một yêu cầu được gửi đến API với các tham số phân trang, một chỉ báo tải (spinner ở chân trang) được hiển thị trên giao diện. Chèn: các phần tử mới được thêm vào bộ chuyển đổi, vị trí cuộn được điều chỉnh để tránh nhảy.
Một khía cạnh quan trọng là debounce yêu cầu. Nếu người dùng cuộn nhanh đến cuối, bộ kích hoạt có thể kích hoạt nhiều lần trước khi nhận được phản hồi từ máy chủ. Không có debounce, điều này dẫn đến các yêu cầu trùng lặp (điều kiện cạnh tranh). Giải pháp là chặn các yêu cầu mới cho đến khi yêu cầu trước hoàn thành. Cờ isLoading trong ViewModel ngăn chặn nhiều lần gọi: đặt isLoading = true khi gửi yêu cầu, đặt lại khi nhận được phản hồi hoặc lỗi.
Trên các nền tảng di động, các cơ chế chuyên dụng được sử dụng cho Cuộn vô hạn. Trên iOS, đó là prefetchDataSource trong UICollectionView, tự động yêu cầu dữ liệu cho các ô ngoài màn hình. Trên Android, đó là Paging 3 Library của Google, cung cấp kiến trúc có sẵn với PagingSource, PagingData và PagingDataAdapter. Paging 3 hỗ trợ RemoteMediator để kết hợp dữ liệu mạng và dữ liệu cục bộ và tự động quản lý trạng thái tải.
Phân trang dựa trên độ lệch (offset-based) sử dụng các tham số trang và kích thước: page=2, size=20 trả về các bản ghi 21–40. Cách tiếp cận này đơn giản để triển khai nhưng có một vấn đề cơ bản — nếu các bản ghi được thêm hoặc xóa khỏi cơ sở dữ liệu giữa các yêu cầu, độ lệch sẽ bị sai lệch (người dùng thấy các mục trùng lặp hoặc khoảng trống). Đối với các nguồn cấp có tần suất thay đổi cao (tin tức, bình luận), phân trang dựa trên độ lệch cho kết quả không chính xác.
Phân trang dựa trên con trỏ (cursor-based) sử dụng một định danh duy nhất của phần tử cuối cùng (con trỏ): after=id_12345&limit=20. Máy chủ trả về 20 bản ghi sau con trỏ được chỉ định. Cách tiếp cận này đảm bảo tính nhất quán dữ liệu bất kể các thao tác chèn và xóa. Theo GraphQL Best Practices (2024), phân trang dựa trên con trỏ được khuyến nghị cho tất cả các ứng dụng thời gian thực nơi dữ liệu thay đổi động.
Việc lựa chọn giữa các cách tiếp cận phụ thuộc vào loại ứng dụng. Đối với mạng xã hội (Instagram, TikTok) — chỉ dựa trên con trỏ, vì nguồn cấp được cập nhật liên tục. Đối với danh mục có ít thay đổi (danh mục sản phẩm cửa hàng trực tuyến), phân trang dựa trên độ lệch có thể chấp nhận được. Đối với các kịch bản kết hợp, Google khuyến nghị Paging 3 RemoteMediator, kết hợp phân trang dựa trên con trỏ từ mạng với phân trang dựa trên độ lệch từ cơ sở dữ liệu Room cục bộ.
Trên Android, cách tiếp cận tiêu chuẩn là thư viện Paging 3 từ Jetpack. PagingSource xác định nguồn dữ liệu (mạng hoặc cơ sở dữ liệu), PagingData chứa các khối dữ liệu và PagingDataAdapter hiển thị chúng trong RecyclerView. Paging 3 tự động quản lý khoảng cách tải trước, thử lại và làm mới. Để tích hợp mạng, RemoteMediator được sử dụng: nó tải dữ liệu từ API, lưu vào Room và thông báo cho PagingSource về các bản cập nhật. Theo Google I/O 2024, hơn 60% ứng dụng Android có Cuộn vô hạn sử dụng Paging 3.
Ví dụ về triển khai Paging 3 cơ bản:
class FeedPagingSource(
private val api: FeedApi
) : PagingSource<String, Post>() {
override suspend fun load(
params: LoadParams<String>
): LoadResult<String, Post> {
val response = api.getFeed(
cursor = params.key,
limit = params.loadSize
)
return LoadResult.Page(
data = response.items,
prevKey = null,
nextKey = response.nextCursor
)
}
}
Trên iOS, Cuộn vô hạn được triển khai bằng UICollectionView với prefetchDataSource. Giao thức UICollectionViewDataSourcePrefetching chứa phương thức collectionView(_:prefetchItemsAt:), được gọi khi hệ thống dự đoán cuộn đến các index paths nhất định. Không giống Android Paging 3, iOS không có thư viện phân trang tích hợp sẵn — các nhà phát triển triển khai thủ công hoặc sử dụng các giải pháp của bên thứ ba như RxSwift + NSLayoutConstraint hoặc các đường ống dựa trên Combine.
Ví dụ về tải trước trên iOS:
extension FeedViewController: UICollectionViewDataSourcePrefetching {
func collectionView(
_ collectionView: UICollectionView,
prefetchItemsAt indexPaths: [IndexPath]
) {
let lastRow = collectionView.numberOfItems(inSection: 0) - 1
if indexPaths.contains(IndexPath(row: lastRow, section: 0)) {
viewModel.loadNextPage()
}
}
}
SwiftUI cung cấp một cách tiếp cận khai báo hơn thông qua bộ sửa đổi onAppear. Nhà phát triển đặt ProgressView ở cuối danh sách và khi nó xuất hiện, kích hoạt tải trang tiếp theo. Theo Apple WWDC 2024, các API mới AsyncSequence và Swift Algorithms đơn giản hóa việc triển khai Cuộn vô hạn bằng cách cung cấp các toán tử chunking và debounce tích hợp sẵn.
Vấn đề UX chính của Cuộn vô hạn là mất chân trang và điều hướng. Trong các cửa hàng trực tuyến, người dùng thường muốn đi đến chân trang để tìm thông tin liên hệ hoặc liên kết. Cuộn vô hạn làm cho chân trang không thể truy cập được — nó liên tục di chuyển xuống dưới khi có thêm nội dung được tải. Giải pháp là thêm một nút hành động nổi (FAB) để cuộn nhanh lên trên hoặc cố định chân trang riêng biệt với danh sách.
Vấn đề thứ hai là thiếu lịch sử cuộn. Nếu người dùng nhìn thấy một sản phẩm thú vị ở vị trí 3, cuộn đến vị trí 50 và sau đó nhấn «Quay lại» — họ quay lại đầu danh sách và phải cuộn lại đến vị trí 50. Giải pháp là lưu vị trí cuộn trong ViewModel hoặc sử dụng khôi phục trạng thái ở cấp Activity/UIViewController. iOS hỗ trợ NSUserActivity để khôi phục vị trí, Android hỗ trợ onSaveInstanceState.
Vấn đề thứ ba là hiệu suất với hàng ngàn phần tử. Nếu không cấu hình ảo hóa, sau 500–1000 phần tử được tải, ứng dụng bắt đầu chậm do tăng mức tiêu thụ bộ nhớ. Giải pháp là sử dụng ảo hóa với RecyclerView hoặc UICollectionView, chỉ giữ trong bộ nhớ các ô hiển thị và được tải trước. Việc dọn dẹp định kỳ dữ liệu cũ (loại bỏ các trang vượt quá N trang) cũng làm giảm tải.
Câu hỏi thường gặp
Cuộn vô hạn (Infinite Scroll) là kỹ thuật tự động tải nội dung khi người dùng chạm đến cuối danh sách. Dữ liệu mới được thêm vào một cách liền mạch mà không cần nhấp vào các nút phân trang. Nó được sử dụng trong mạng xã hội, nguồn tin tức và danh mục có nội dung động.
Phân trang yêu cầu điều hướng thủ công giữa các trang (các nút «1, 2, 3»), trong khi Infinite Scroll tự động tải dữ liệu. Phân trang có thể dự đoán và bảo toàn ngữ cảnh điều hướng; Cuộn vô hạn làm tăng mức độ tương tác nhưng làm phức tạp việc truy cập vào chân trang và lịch sử cuộn. Lựa chọn phụ thuộc vào loại nội dung và mục tiêu của ứng dụng.
Trên Android, thư viện được khuyến nghị là Paging 3 từ Jetpack. Nó cung cấp PagingSource cho nguồn dữ liệu, PagingData cho các khối và PagingDataAdapter cho RecyclerView. Paging 3 tự động quản lý tải trước, trạng thái tải và thử lại. Đối với các kịch bản kết hợp ngoại tuyến/trực tuyến, hãy sử dụng RemoteMediator.
Các yêu cầu trùng lặp được ngăn chặn thông qua cờ debounce isLoading. Khi yêu cầu đầu tiên được gửi, cờ được đặt thành true và chặn các lần gọi mới cho đến khi nhận được phản hồi. Sau phản hồi thành công, cờ được đặt lại. Ngoài ra, bạn có thể hủy các coroutine (Kotlin) hoặc Cancellable (Swift) khi cuộn ngược lại.
Cuộn vô hạn không phù hợp cho thương mại điện tử có tìm kiếm và so sánh sản phẩm, cho các ứng dụng có chân trang quan trọng (liên hệ, liên kết), cho các trang kết quả tìm kiếm (người dùng cần quay lại mục cụ thể) và cho các trang thống kê/báo cáo nơi tổng số lượng quan trọng. Trong những trường hợp này, hãy sử dụng phân trang cổ điển hoặc nút «Tải thêm».
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm