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

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

Intersection Observer phiên bản 1 là một trong những API đó có thể được yêu thích trên toàn cầu và giờ đây Safari cũng hỗ trợ, cuối cùng nó cũng có thể sử dụng được trên toàn cầu trong tất cả các trình duyệt chính. Để ôn lại nhanh về API, Tôi khuyên bạn nên xem Surma Microtip tăng áp trên giao lộ Trình quan sát phiên bản 1 đượ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 bài viết này. Mọi người đã dùng Intersection Observer phiên bản 1 cho nhiều trường hợp như tải từng phần hình ảnh và video, được thông báo khi các phần tử đạt đến position: sticky, kích hoạt sự kiện phân tích, và nhiều lợi ích khác.

Để biết toàn bộ thông tin chi tiết, hãy xem Tài liệu về Intersection Observer trên MDN, nhưng xin lưu ý rằng đây là giao diện của API Intersection Observer v1 nhất trường hợp cơ bản:

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'));

Thách thức của Intersection Observer phiên bản 1 là gì?

Để cho rõ ràng, Intersection Observer v1 rất tuyệt vời, nhưng chưa hoàn hảo. Có một số trường hợp khó hiểu mà API thiếu. Hãy cùng tìm hiểu kỹ hơn nhé! API Intersection Observer phiên bản 1 có thể cho bạn biết khi nào 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 phủ hay không bởi bất kỳ nội dung trang nào khác (tức là khi phần tử bị che khuất) hoặc cách hiển thị trực quan của phần tử đã được sửa đổi bằng các hiệu ứng hình ảnh như transform, opacity, filter, v.v. có thể giúp ẩn hiệu quả.

Đối với một phần tử trong tài liệu cấp cao nhất, thông tin này có thể được xác định bằng cách phân tích DOM qua JavaScript, ví dụ qua DocumentOrShadowRoot.elementFromPoint() rồi tìm hiểu sâu hơn. Ngược lại, không thể thu được cùng một thông tin nếu yếu tố được đề cập là đặt 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?

Thật không may, Internet là nơi thu hút những đối tượng xấu có ý định xấu hơn. Ví dụ: nhà xuất bản khả nghi phân phát quảng cáo trả tiền cho mỗi lần nhấp chuột trên 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 số tiền thanh toán cho quảng cáo của nhà xuất bản (ít nhất trong một khoảng thời gian ngắn cho đến khi mạng quảng cáo nắm bắt được). Thông thường, những quảng cáo như vậy được phân phát trong iframe. 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ể tạo 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ủ lên các iframe bên cạnh nội dung nào đó hấp dẫn, chẳng hạn như video mèo đáng yêu mà người dùng thực sự muốn nhấp vào. Quá trình này được gọi là clickjacking. Bạn có thể thấy một cuộc tấn công bằng nhấp chuột như vậy ở phần trên của video này minh hoạ (thử "xem" video về mèo rồi kích hoạt "chế độ lừa gạt"). Bạn sẽ nhận thấy rằng quảng cáo trong iframe "nghĩ" quảng cáo đó đã nhận được các lượt nhấp hợp lệ, ngay cả khi hoàn toàn trong suốt khi bạn nhấp vào (giả vờ không tự nguyện) nhấp và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ủ quảng cáo lên trên một thứ gì đó hấp dẫn.

Intersection Observer v2 khắc phục được vấn đề này?

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

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

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

Trong thực tế, mã mới trông như thế nào?

Hàm khởi tạo IntersectionObserver hiện có thêm 2 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ừ đối tượng tiếp nhận dữ liệu cho một mục tiêu cụ thể. trackVisibility là boolean cho biết liệu trình quan sát có theo dõi các thay đổi trong khả năng hiển thị.

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

Theo spec, chế độ hiển thị là được tính như sau:

  • Nếu thuộc tính trackVisibility của đối tượng tiếp nhận dữ liệu là false, thì mục tiêu được coi là nhìn thấy được. Điều này tương ứng với hành vi phiên bản 1 hiện tại.

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

  • 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à vô hình.

  • 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ất kỳ bộ lọc nào, thì mục tiêu được coi là vô hình.

  • Nếu việc triển khai không thể đảm bảo rằng mục tiêu hoàn toàn không bị trang khác che khuất thì mục tiêu được coi là không nhìn thấy được.

Điều này có nghĩa là các phương pháp 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ụ: áp dụng bộ lọc thang màu xám gần như không được chú ý như filter: grayscale(0.01%) hoặc việc thiết lập độ trong suốt gần như vô hình bằng opacity: 0.99 đều sẽ kết xuất phần tử không hiển thị.

Dưới đây là mã mẫu ngắn minh hoạ các tính năng API mới. Bạn có thể xem cách theo dõi số lượt nhấp logic trong thực tế trong phần thứ hai của bản minh hoạ (nhưng bây giờ, hãy thử "xem" video về cún con). Hãy nhớ kích hoạt "chế độ lừa đảo" lần nữa thành ngay tự 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 lượt nhấp vào quảng cáo không hợp lệ bị theo dõi. Lần này, Intersection Observer phiên bản 2 có sự hỗ trợ của chúng ta! 🎉

Giao diện Observer phiên bản 2 ngăn chặn lượt nhấp không chủ ý 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'));

Xác nhận

Nhờ có Simeon Vincent, Yoav WeissMathias Bynens để xem 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.