2022.09.28

Hãy cùng tìm hiểu về Infinite Scroll với Intersection Observer

Infinite Scroll với Intersection Observer

Chào mừng các bạn đã ghé thăm trang blog của Market Enterprise chúng mình. Hôm nay chúng ta hãy cùng nhau tìm hiểu về Infinite Scroll với Intersection Observer nhé!

Trước khi có Intersection Observer

Trước đây để làm infinite scroll chúng ta thường sử dụng các thuộc tính vị trí để tính toán khi nào thì lấy thêm dữ liệu để thêm vào danh sách các item đang hiển thị trên màn hình. 

Ví dụ:

ví dụ về Infinite Scroll

  1. window.innerHeight — cố định (static), chiều cao của cửa sổ trình duyệt
  2. window.scrollY — di chuyển (dynamic), vị trí hiện tại của thanh scroll.
  3. list.clientHeight — cố định (static), chiều cao của phần từ <ul> <ul/>.
  4. list.offsetTop — cố định (static) khoảng cách từ đầu trang tới phần tử <ul>.
  • Nếu (1) + (2) === (3) + (4), chúng hiểu rằng người dùng đã cuộn đến cuối trang và cần nạp thêm dữ liệu.

Trong trường hợp phần tử ul được fix cứng chiều cao. Chúng ta cần lấy các thuộc tính của phần từ này.

  1. element.scrollHeight — tổng chiều cao có thể cuộn trang.
  2. element.scrollTop — vị trí hiện tại của thanh scroll.
  3. element.clientHeight — chiều cao hiện tại của phần từ (đã bao gồm overflow).
  • Vậy thì khi (1) === (2) + (3) người dùng sẽ chạm đến cuối trang.

Từ ví dụ trên có thể thấy việc tính toán khi nào thì người dùng cuộn tới cuối trang sẽ phải phân theo từng trường hợp. 

Mỗi trường hợp lại có các phép tính toán riêng, chưa kể các thuộc tính về vị trí và chiều cao được sử dụng ở ví dụ trên không phải ai cũng biết là có thể phân biệt rõ ràng. Từ đó dễ gây nhầm lẫn trong lúc xây dựng hàm tính toán. 

Thêm vào đó nếu áp dụng phương pháp này vào react thì dễ phát sinh các lỗi logic không mong muốn mà một người mới học react sẽ không để ý và khó debug được. Ví dụ như khi fetch thêm dữ liệu thì app sẽ được render lại, từ đó hàm tính toán cũng được tạo lại, dẫn tới app bị add thêm một sự kiện window.addEventListener nên buộc phải thêm điều kiện.

Chỉ một việc fetch thêm dữ liệu để hiển thị tưởng chừng như đơn giản, nhưng nếu áp dụng cách trên thì logic lại khá phức tạp và phát sinh nhiều trường hợp mà khi viết các hàm xử lý logic phải cực kỳ để ý.

Ý tưởng: Chúng ta đặt một thứ gì đó để nó quan sát app của chúng ta, khi nào thông tin cuối cùng của trang được hiện lên thì thực hiện lấy thêm dữ liệu, sau đó thêm vào danh sách cần hiển thị. Với các yêu cầu như trên chúng ta có thể dùng Intersection Observer thay cho việc tính toán vị trí. 

Intersection Observer là gì?

“The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document’s viewport.”

Intersection Observer API cung cấp một cách để quan sát không đồng bộ các thay đổi trong giao diện với một ancestor element hay với một document viewport cấp cao – Theo MDN.

(viewport: là một khung hình, có thể là một khu vực hình đa giác (thường là hình chữ nhật) trên màn hình mà bạn được nhìn thấy. 

Trong trình duyệt web, nó đề cập đến phần document mà người dùng đang nhìn thấy, cái hiển thị trong cửa sổ ứng dụng (hoặc trên màn hình nếu hiển thị ở dạng full screen). 

Các phần bên ngoài viewport là những thứ bạn không nhìn thấy, cho đến khi bạn scroll tới nó.

Viewport

Các tính năng có thể áp dụng Intersection Observer

  • Lazy-loading hình ảnh hay các thông tin khác khi trang được cuộn.
  • Thực hiện “infinite scrolling” trang web, nơi mà càng ngày càng nhiều thông tin được tải về và hiển thị khi bạn cuộn để người dùng không phải lật qua các trang.
  • Báo cáo về khả năng hiển thị của quảng cáo để tính toán doanh thu quảng cáo.
  • Quyết định có thực hiện các tác vụ hoặc hoạt ảnh hay không dựa trên việc người dùng có nhìn thấy nó hay không.

Các bước thử nghiệm khởi tạo một dự án

Bước 1: Khởi tạo dự án

Đầu tiên là tạo một project react thông qua câu lệnh quen thuộc

npx create-react-app infinite-scrolling

npm i axios

đợi cho project được tạo ra sau đó chạy lệnh 

npm start

Learn React

Được một màn hình có giao diện như bên trên

Bước 2: Viết code render ra một danh sách tiêu đề các quyển sách có trong thư viện

Lấy dữ liệu 

Bắt đầu chỉnh sửa file App.js

Đầu tiên cần có dữ liệu trước đã, chúng ta hãy dùng axios để thử lấy dữ liệu nào

File App.js

Chạy thử code và nếu màn hình hiển thị như bên dưới là đã lấy được data

Ví dụ Infinite Scroll với Intersection Observer

Hiển thị data lên màn hình

Dùng state để lưu trữ danh sách các quyển sách đã lấy được

import { useEffect, useState } from 'react';

const [books, setBooks] = useState([]);

lưu danh sách các quyển sách đã lấy được vào biến books

  • Lưu ý: để tránh rơi vào vòng lặp render vô hạn chúng ta cần đặt điều kiện cho useEffect để nó chỉ chạy một lần đâu tiên lúc render để chúng ta có thể lấy được dữ liệu thôi, hãy thêm một mảng rỗng vào sau callback.

Dùng hàm map cho danh sách các quyển sách để hiển thị tiêu đề của các quyển sách lên màn hình thôi nào

books.map((book, index) => {
        return <div key={book.key} >{book.title}</div>;
      })

Lưu ý: thêm key cho mỗi phần tử <div key={book.key}>{book.title}</div> 

Ta có code như hình dưới

File App.js

Kết quả của code này được hiển thị lên trình duyệt như hình dưới

Absolution

 

Bước 3: Đặt IntersectionObserver và gắn đối tượng cần quan sát vào IntersectionObserver

Câu hỏi đặt ra tiếp theo, làm sao để khi cuộn xuống cuối trang thì fetch thêm dữ liệu?

Phương thức ở đây khá đơn giản, đó chính là đặt một đối tượng để quan sát phần tử cuối cùng trong list của chúng ta. Nếu phần tử đó xuất hiện trên màn hình thì có nghĩa là người dùng đã cuộn tới cuối trang.

Đối tượng giúp chúng ta quan sát đó chính là IntersectionObserver và đối tượng cần quan sát đó chính là thẻ div cuối cùng trong danh sách.

Chúng ta sẽ lợi dụng thuộc tính ref để truyền thẻ div cuối cùng trong danh sách vào hàm lastBookElementRef để thêm nó vào danh sách mà IntersectionObserver cần quan sát. 

IntersectionObserver cần được khai báo bên ngoài hàm lastBookElementRef để tránh việc người quan sát của chúng ta bị kill khi hàm thực thi xong. Và để từ bên trong hàm lastBookElementRef gọi được đến biến observer nằm bên ngoài chúng ta cần sử dụng useRef.

Dùng new IntersectionObserver để cài một hàm vào trong observer, hàm này sẽ được thực thi mỗi khi observer thấy trạng thái của đối tượng quan sát có sự thay đổi.

Ví dụ Infinite Scroll với Intersection Observer 1

Ví dụ Infinite Scroll với Intersection Observer 1b

 

Như vậy chúng ta đã bắt được sự kiện khi nào người dùng cuộn tới cuối trang

Bước 4: Cập nhật danh sách khi cuộn đến cuối trang

Tiếp theo, khi người dùng cuộn tới cuối trang thì chúng ta phải tăng số trang lên +1 để lấy thêm dữ liệu về và cập nhật vào danh sách books

Ví dụ Infinite Scroll với Intersection Observer 2

 

Ví dụ Infinite Scroll với Intersection Observer 2b

Chúng ta đã load được thêm dữ liệu khi phần từ nằm cuối danh sách xuất hiện trên màn hình, tuy nhiên đang bị một lỗi khá nghiêm trọng là app của chúng ta liên tục fetch thêm dữ liệu không ngừng. 

Vấn đề này xảy ra do App không biết khi nào nên gắn bộ theo dõi vào phần tử cuối cùng. Vì nếu cứ sau mỗi lần render đều gắn bộ theo dõi vào thì sẽ xuất hiện trường hợp chưa kịp lấy thêm dữ liệu thì đã render xong và gắn bộ theo dõi vào, và phần tử đó lại tiếp tục xuất hiện trên màn hình cho nên lại kéo thêm dữ liệu và render tiếp

Để khắc phục điều này ta thêm một biến loading để kiểm tra khi nào nên gắn bộ theo dõi vào phần tử cuối cùng.

Ví dụ Infinite Scroll với Intersection Observer 3

Cám ơn các bạn đã theo dõi bài viết của mình. Nếu các bạn chưa biết về reactjs hoặc cần tìm hiểu thêm về các vấn đề cơ bản của react thì có thể tham khảo thêm bài viết 1 số đặc điểm cơ bản của React này nhé!

0 Comments
Inline Feedbacks
View all comments