Lòng tin tốt, quan sát tốt hơn: Intersection Observer phiên bản 2

Intersection Observer v2 không chỉ có khả năng quan sát các giao lộ mà còn có thể phát hiện xem phần tử giao nhau có hiển thị tại thời điểm giao nhau hay không.

Intersection Observer v1 là một trong những API được yêu thích rộng rãi. Giờ đây, khi Safari cũng hỗ trợ API này, bạn có thể sử dụng API này trên tất cả các trình duyệt chính. Để ôn lại nhanh về API này, bạn nên xem Supercharged Microtip (Mẹo nhỏ siêu tăng tốc) của Surma về Intersection Observer v1 được nhúng bên dưới. Bạn cũng có thể đọc bài viết chuyên sâu của Surma. Mọi người đã sử dụng Intersection Observer v1 cho nhiều trường hợp sử dụng như tải lười hình ảnh và video, được thông báo khi các phần tử đạt đến position: sticky, kích hoạt các sự kiện phân tích, và nhiều trường hợp khác.

Để biết thông tin đầy đủ, hãy xem tài liệu về Intersection Observer trên MDN. Tuy nhiên, xin lưu ý ngắn gọn rằng API Intersection Observer v1 có dạng như sau trong trường hợp cơ bản nhất:

const onIntersection = (entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      console.log(entry);
    }
  }
};

const observer = new IntersectionObserver(onIntersection);
observer.observe(document.querySelector('#some-target'));

Intersection Observer v1 có gì khó khăn?

Xin nói rõ rằng Intersection Observer v1 rất tuyệt nhưng chưa hoàn hảo. Có một số trường hợp khó xử mà API không đáp ứng được. Hãy cùng tìm hiểu kỹ hơn! API Intersection Observer v1 có thể cho bạn biết thời điểm một phần tử được cuộn vào khung nhìn của cửa sổ, nhưng không cho bạn biết liệu phần tử đó có bị che khuất bởi bất kỳ nội dung trang nào khác hay không (tức là khi phần tử bị che khuất) hoặc liệu chế độ hiển thị hình ảnh của phần tử đó có bị sửa đổi bởi các hiệu ứng hình ảnh như transform, opacity, filter, v.v. hay không, điều này có thể hiệu quả khiến phần tử đó không hiển thị.

Đối với một phần tử trong tài liệu cấp cao nhất, bạn có thể xác định thông tin này bằng cách phân tích DOM thông qua JavaScript, ví dụ: thông qua DocumentOrShadowRoot.elementFromPoint() rồi tìm hiểu sâu hơn. Ngược lại, bạn không thể lấy được thông tin tương tự nếu phần tử có liên quan nằm trong iframe của bên thứ ba.

Tại sao khả năng hiển thị thực tế lại quan trọng đến vậy?

Rất tiếc, Internet là nơi thu hút những kẻ xấu có ý định xấu xa hơn. Ví dụ: một nhà xuất bản mờ ám phân phát quảng cáo trả tiền cho mỗi lượt nhấp trên một trang web nội dung có thể được khuyến khích để lừa mọi người nhấp vào quảng cáo của họ nhằm tăng khoản thanh toán quảng cáo của nhà xuất bản (ít nhất là trong một khoảng thời gian ngắn, cho đến khi mạng quảng cáo phát hiện ra). Thông thường, những quảng cáo như vậy được phân phát trong iframe. Giờ đây, nếu nhà xuất bản muốn người dùng nhấp vào những quảng cáo như vậy, họ có thể làm cho iframe quảng cáo hoàn toàn trong suốt bằng cách áp dụng quy tắc CSS iframe { opacity: 0; } và phủ các iframe lên một nội dung hấp dẫn, chẳng hạn như video về chú mèo dễ thương mà người dùng thực sự muốn nhấp vào. Đây được gọi là clickjacking. Bạn có thể xem một cuộc tấn công giả mạo lượt nhấp như vậy trong phần trên của bản minh hoạ này (hãy thử "xem" video về mèo và kích hoạt "chế độ lừa đảo"). Bạn sẽ nhận thấy rằng quảng cáo trong iframe "nghĩ" rằng nó đã nhận được các lượt nhấp hợp lệ, ngay cả khi quảng cáo đó hoàn toàn minh bạch khi bạn (giả vờ không tự nguyện) nhấp vào quảng cáo đó.

Lừa người dùng nhấp vào quảng cáo bằng cách tạo kiểu trong suốt và phủ lên một nội dung hấp dẫn.

Intersection Observer v2 khắc phục vấn đề này như thế nào?

Intersection Observer v2 giới thiệu khái niệm theo dõi "khả năng hiển thị" thực tế của một phần tử mục tiêu như cách con người xác định. Bằng cách đặt một tuỳ chọn trong hàm khởi tạo IntersectionObserver, các thực thể giao nhau IntersectionObserverEntry sẽ chứa một trường boolean mới có tên là isVisible. Giá trị true cho isVisible là một đảm bảo chắc chắn từ quá trình triển khai cơ bản rằng phần tử mục tiêu không bị nội dung khác che khuất hoàn toàn và không có hiệu ứng hình ảnh nào được áp dụng sẽ làm thay đổi hoặc làm méo màn hình hiển thị của phần tử đó. Ngược lại, giá trị false có nghĩa là cách triển khai không thể đảm bảo điều đó.

Một chi tiết quan trọng của thông số kỹ thuật là việc triển khai được phép báo cáo kết quả âm tính giả (tức là đặt isVisible thành false ngay cả khi phần tử mục tiêu hoàn toàn hiển thị và không bị sửa đổi). Vì hiệu suất hoặc các lý do khác, trình duyệt chỉ giới hạn ở việc làm việc với hộp giới hạn và hình học đường thẳng; trình duyệt không cố gắng đạt được kết quả hoàn hảo về pixel cho các nội dung sửa đổi như border-radius.

Tuy nhiên, không được phép dấu dương tính giả trong bất kỳ trường hợp nào (tức là đặt isVisible thành true khi phần tử mục tiêu không hiển thị hoàn toàn và không được sửa đổi).

Mã mới sẽ có dạng như thế nào trong thực tế?

Hàm khởi tạo IntersectionObserver hiện có thêm hai thuộc tính cấu hình: delaytrackVisibility. delay là một số cho biết độ trễ tối thiểu tính bằng mili giây giữa các thông báo từ trình quan sát cho một mục tiêu nhất định. trackVisibility là một boolean cho biết đối tượng tiếp nhận dữ liệu có theo dõi các thay đổi về chế độ hiển thị của mục tiêu hay không.

Điều quan trọng cần lưu ý ở đây là khi trackVisibilitytrue, delay phải có giá trị tối thiểu là 100 (tức là không quá một thông báo mỗi 100 mili giây). Như đã lưu ý trước đó, việc tính toán chế độ hiển thị sẽ tốn kém và yêu cầu này là một biện pháp phòng ngừa để giảm thiểu mức tiêu thụ pin và hiệu suất. Nhà phát triển có trách nhiệm sẽ sử dụng giá trị lớn nhất có thể chấp nhận được cho độ trễ.

Theo thông số kỹ thuật hiện tại, chế độ hiển thị được tính như sau:

  • Nếu thuộc tính trackVisibility của trình quan sát là false, thì mục tiêu được coi là hiển thị. Điều này tương ứng với hành vi hiện tại của phiên bản 1.

  • Nếu mục tiêu có một ma trận biến đổi hiệu quả khác với phép dịch 2D hoặc phép nâng cấp 2D theo tỷ lệ, thì mục tiêu đó được coi là không hiển thị.

  • Nếu mục tiêu hoặc bất kỳ phần tử nào trong chuỗi khối chứa mục tiêu đó có độ mờ hiệu quả khác với 1.0, thì mục tiêu đó được coi là không hiển thị.

  • Nếu mục tiêu hoặc bất kỳ phần tử nào trong chuỗi khối chứa mục tiêu đó được áp dụng bộ lọc, thì mục tiêu đó được coi là không hiển thị.

  • Nếu phương thức triển khai không thể đảm bảo rằng mục tiêu không bị nội dung trang khác che khuất hoàn toàn, thì mục tiêu đó được coi là không hiển thị.

Điều này có nghĩa là các phương thức triển khai hiện tại khá thận trọng trong việc đảm bảo khả năng hiển thị. Ví dụ: việc áp dụng bộ lọc thang màu xám gần như không thể nhận thấy như filter: grayscale(0.01%) hoặc đặt độ trong suốt gần như không thể nhận thấy bằng opacity: 0.99 đều sẽ hiển thị phần tử không nhìn thấy.

Dưới đây là một đoạn mã mẫu ngắn minh hoạ các tính năng API mới. Bạn có thể xem logic theo dõi lượt nhấp trong phần thứ hai của bản minh hoạ (nhưng bây giờ, hãy thử "xem" video về chú chó con). Hãy nhớ kích hoạt lại "chế độ lừa đảo" để ngay lập tức chuyển đổi thành một nhà xuất bản mờ ám và xem cách Intersection Observer v2 ngăn chặn việc theo dõi các lượt nhấp vào quảng cáo không hợp lệ. Lần này, Intersection Observer v2 sẽ giúp chúng ta! 🎉

Intersection Observer v2 ngăn chặn lượt nhấp không mong muốn vào quảng cáo.

<!DOCTYPE html>
<!-- This is the ad running in the iframe -->
<button id="callToActionButton">Buy now!</button>
// This is code running in the iframe.

// The iframe must be visible for at least 800ms prior to an input event
// for the input event to be considered valid.
const minimumVisibleDuration = 800;

// Keep track of when the button transitioned to a visible state.
let visibleSince = 0;

const button = document.querySelector('#callToActionButton');
button.addEventListener('click', (event) => {
  if ((visibleSince > 0) &&
      (performance.now() - visibleSince >= minimumVisibleDuration)) {
    trackAdClick();
  } else {
    rejectAdClick();
  }
});

const observer = new IntersectionObserver((changes) => {
  for (const change of changes) {
    // ⚠️ Feature detection
    if (typeof change.isVisible === 'undefined') {
      // The browser doesn't support Intersection Observer v2, falling back to v1 behavior.
      change.isVisible = true;
    }
    if (change.isIntersecting && change.isVisible) {
      visibleSince = change.time;
    } else {
      visibleSince = 0;
    }
  }
}, {
  threshold: [1.0],
  // 🆕 Track the actual visibility of the element
  trackVisibility: true,
  // 🆕 Set a minimum delay between notifications
  delay: 100
}));

// Require that the entire iframe be visible.
observer.observe(document.querySelector('#ad'));

Lời cảm ơn

Cảm ơn Simeon Vincent, Yoav WeissMathias Bynens đã xem xét bài viết này, cũng như Stefan Zager đã xem xét và triển khai tính năng này trong Chrome. Hình ảnh chính của Sergey Semin trên Unsplash.