Intersection Observer API

Bài viết được sự cho phép của tác giả Lưu Bình An

Chúng ta thường phải đặt listener trên sự kiện window.scroll thực hiện một số thao tác tính toán, so sánh với thanh scroll để biết được khi nào element bắt đầu xuất hiện.

Cách làm này gây nhiều vấn đề hiệu năng và tương đối rườm rà. Giờ các trình duyệt đã đồng loạt hỗ trợ Intersection Observer API, chúng ta có một cách hoàn toàn gọn gàng, sạch sẽ mà lại tối ưu hiệu năng hơn nhiều.

  Bàn luận về setTimeout và setInterval trong javascript
  Cấu hình JSP views trong InternalResourceViewResolver với Spring Boot JAR file

Cách sử dụng như sau, chúng ta khởi tạo một instance IntersectionObserver và gọi observe trên element muốn theo dõi (watch là thuật ngữ chuyên ngành hơn)

const myImg = document.querySelector('.animate-me');

const observer = new IntersectionObserver((entry, observer) => {
    console.log({ entry });
    console.log({ observer });
})

observer.observe(myImg);

Trong trường hợp chúng ta muốn observe trên nhiều element cùng lúc

const myImgs = document.querySelectorAll('.animate-me');

const observer = new IntersectionObserver(entries => {
  console.log(entries);
});

myImgs.forEach(image => {
  observer.observe(image);
});

Để thực thi một tác vụ nào đó khi element bắt đầu xuất hiện trong viewport hoặc leave khỏi viewport

const myImgs = document.querySelectorAll('.animate-me');

observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (entry.intersectionRatio > 0) {
      console.log('in the view');
    } else {
      console.log('out of view');
    }
  });
});

myImgs.forEach(image => {
  observer.observe(image);
});

Với điều kiện intersectionRatio > 0 chúng ta biết được element đã xuất hiện trong viewport hay không

Với lazy load, chúng ta chỉ cần observe ở lần đầu tiên khi xuất hiện trên viewport, chúng ta sẽ unobserve nó đi vì không cần tracking tiếp nữa

const myImgs = document.querySelectorAll('.animate-me');

observer = new IntersectionObserver(entries => {
  entries.forEach(entry => {
    if (entry.intersectionRatio > 0) {
      console.log('in the view');
      observer.unobserve(entry.target);
    } else {
      console.log('out of view');
    }
  });
});

myImgs.forEach(image => {
  observer.observe(image);
});

IntersectionObserver API nhận thêm params thứ 2, để chúng ta truyền một số config

const config = {
  rootMargin: '50px 20px 75px 30px',
  threshold: [0, 0.25, 0.75, 1]
};

const observer = new IntersectionObserver(entry => {
  // ...
}, config);

Các giá trị có thể truyền vào cho config

  • root element dùng để kiểm tra intersection, nếu null nó sẽ lấy document viewport
  • rootMargin: khai báo như giá trị margin css, ví dụ 3rem 2rem, có thể dùng để thêm offset cho intersection point
  • threhold: mảng giá trị từ 0 đến 1, tương ứng với ratio xuất hiện của element, 0 = hoàn toàn ra khỏi viewport, 1 là đang nằm trong viewport hoàn toàn, callback sẽ được gọi vào tất cả các giá trị đã khai báo

Element được xem là nằm ngoài viewport khi nó đã nằm ngoài viewport + 15px margin

Ứng dụng 1: lazy load image

let observer = new IntersectionObserver(
(entries, observer) => { 
entries.forEach(entry => {
    /* Placeholder replacement */
    entry.target.src = entry.target.dataset.src;
    observer.unobserve(entry.target);
  });
}, 
{rootMargin: "0px 0px -200px 0px"});

document.querySelectorAll('img').forEach(img => { observer.observe(img) });

Ứng dụng 2: Tự động pause video khi ra khỏi màn hình

let video = document.querySelector('video');
let isPaused = false; /* Flag for auto-paused video */
let observer = new IntersectionObserver((entries, observer) => { 
  entries.forEach(entry => {
    if(entry.intersectionRatio!=1  && !video.paused){
      video.pause(); isPaused = true;
    }
    else if(isPaused) {video.play(); isPaused=false}
  });
}, {threshold: 1});
observer.observe(video);

Ứng dụng 3: Toggle class khi header sticky

const primaryNav = document.getElementById('primaryNav');

function callBack ([e]) {
    e.target.classList.toggle("sticky", e.intersectionRatio < 1)
}

const observer = new IntersectionObserver( 
    callBack,
    { threshold: [1] }
);

observer.observe(primaryNav)
@media (prefers-reduced-motion: no-preference) {
  .scroller {
    scroll-behavior: smooth;
  }
}

Tham khảo

Bài viết gốc được đăng tải tại vuilaptrinh.com

Có thể bạn quan tâm:

Xem thêm Việc làm IT hấp dẫn trên TopDev