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 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?

Để 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ó 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, thông tin này có thể được xác định bằng cách phân tích DOM thông qua JavaScript, chẳng hạn như qua DocumentOrShadowRoot.elementFromPoint() rồi tìm hiểu kỹ 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. Bây giờ, nếu nhà xuất bản muốn thu hút người dùng nhấp vào các 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ủ lên các iframe ở trên một nội dung hấp dẫn, chẳng hạn như một video 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 nhấp tặc 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 "cho rằng" quảng cáo đó đã 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 nhấp vào (giả vờ không tự nguyện).

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à 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 có hiệu ứng hình ảnh nào được áp dụng 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à 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, các trình duyệt tự giới hạn trong việc xử lý các hộp giới hạn và hình học trực tuyến; các trình duyệt này không cố gắng đạt được kết quả pixel hoàn hảo khi chỉnh sửa 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 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ừ trình quan sát cho một mục tiêu nhất định. trackVisibility là giá trị boolean cho biết trình quan sát có theo dõi các thay đổi trong 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 đố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 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 1.0, thì mục tiêu được coi là không nhìn thấy được.

  • 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 quá trình triển khai không thể đảm bảo rằng mục tiêu hoàn toàn không bị nội dung khác trên trang che khuất, thì mục tiêu sẽ đượ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 một bộ lọc thang màu xám gần như không được chú ý như filter: grayscale(0.01%) hoặc thiết lập một độ trong suốt gần như vô hình bằng opacity: 0.99 sẽ khiến phần tử không nhìn thấy được.

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 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 chính mình thành nhà xuất bản khả nghi 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'));

Xác nhậ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.