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

Trình quan sát giao điểm phiên bản 2 bổ sung tính năng không chỉ quan sát các giao lộ mỗi giây, mà còn phát hiện xem có thể nhìn thấy thành phần giao nhau tại thời điểm giao lộ hay không.

Intersection Observer v1 là một trong những API có thể được mọi người yêu thích và hiện tại Safari cũng hỗ trợ, bạn cũng có thể sử dụng API này trên tất cả các trình duyệt chính. Để tìm hiểu nhanh về API, bạn nên xem Surma của Supercharged Microtip trên 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 từng phần hình ảnh và video, nhận 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 trường hợp 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. Tuy nhiên, xin lưu ý rằng trong trường hợp cơ bản nhất, giao diện của Intersection Observer v1 là như sau:

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

Có gì khó với Trình quan sát giao điểm phiên bản 1?

Nói một cách 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 xảy ra ở góc độ API. 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ị bất kỳ nội dung nào khác trên trang che phủ hay không (tức là khi phần tử bị che khuất) hoặc liệu giao diện hình ảnh của phần tử có bị sửa đổi bằng các hiệu ứng hình ảnh như transform, opacity, filter, v.v. hay không. Điều này có thể khiến phần tử đó không hiển thị một cách 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, chẳng hạn như qua DocumentOrShadowRoot.elementFromPoint() và sau đó tìm hiểu kỹ hơn. Ngược lại, bạn không thể lấy cùng một thông tin nếu phần tử được đề cập nằm trong iframe của bên thứ ba.

Vì 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 với ý định xấu. Ví dụ: một nhà xuất bản khả nghi phân phát quảng cáo trả tiền cho mỗi lượt nhấp trên trang web nội dung có thể bị khuyến khích lừa mọi người nhấp vào quảng cáo của họ để tăng tiền thanh toán cho 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 được họ). Thông thường, các 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 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 minh bạch bằng cách áp dụng quy tắc CSS iframe { opacity: 0; } và phủ iframe lên một nội dung 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. Đây được gọi là clickjacking. Bạn có thể xem một cuộc tấn công bằng cách nhấp chuột như vậy trong phần phía trên của bản minh hoạ này (hãy thử "xem" video về mèo rồi 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 trong suốt 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 cho quảng cáo trong suốt và phủ quảng cáo lên trê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?

Giao diện quan sát phiên bản 2 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ư con người sẽ xác định phần tử đó. Bằng cách đặt một tuỳ chọn trong hàm khởi tạo IntersectionObserver, các thực thể IntersectionObserverEntry giao nhau sẽ chứa một trường boolean mới có tên là isVisible. Giá trị true cho isVisible là một 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 sai lệch màn hình hiển thị của phần tử đó 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 thông tin chi tiết quan trọng của spec là quy trình triển khai được phép báo cáo kết quả âm tính giả (nghĩa là đặt isVisible thành false ngay cả khi phần tử mục tiêu hiển thị hoàn toàn và không bị sửa đổi). Vì hiệu suất hoặc vì lý do khác, trình duyệt tự giới hạn trong việc thao tác với hộp giới hạn và hình học trực tuyến; chúng không cố gắng đạt được kết quả hoàn hảo về điểm ảnh cho các nội dung sửa đổi như border-radius.

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

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

Hàm khởi tạo IntersectionObserver hiện nhận 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 giá trị boolean cho biết liệu trình quan sát 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 bắt buộc phải ở ít nhất 100 (nghĩa là không quá một thông báo mỗi 100 mili giây). Như đã đề cập trước đó, việc tính toán khả năng hiển thị rất tốn kém, đồng thời yêu cầu này là để phòng ngừa sự 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 có thể chấp nhận được cho độ trễ.

Theo spec 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à 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 bản dịch 2D hoặc tỷ lệ nâng cấp 2D, thì mục tiêu sẽ đượ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 nó có độ mờ hiệu quả khác 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 bất kỳ bộ lọc nào được áp dụng, thì mục tiêu được coi là không hiển thị.

  • 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ị nội dung trang khác 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 pháp triển khai hiện tại khá thận trọng để đả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 đáng chú ý như filter: grayscale(0.01%) hoặc đặt độ trong suốt gần như không hiển thị bằng opacity: 0.99 đều 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 thực tế ở 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 lại "chế độ lừa đảo" để ngay lập tức chuyển đổi chính bạn thành một 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ẵn sàng! 🎉

Giao diện Observer v2 ngăn chặn lượt nhấp ngoài ý 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 và Mathias Bynens đã xem xét bài viết này, cũng như Stefan Zager vì đã 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.