Доверие — это хорошо, наблюдение — это лучше всего: Intersection Observer v2

Browser Support

  • Хром: 51.
  • Край: 15.
  • Firefox: 55.
  • Сафари: 12.1.

Source

Intersection Observer — один из API, который, пожалуй, любим всеми, и он доступен во всех основных браузерах. Разработчики используют этот API для самых разных целей, включая отложенную загрузку изображений и видео , уведомления о достижении элементами position: sticky , запуск аналитических событий и многое другое.

В самом простом виде API Intersection Observer v1 выглядит так:

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

С помощью API Intersection Observer v1 вы можете узнать, прокручивается ли элемент в область просмотра окна. Однако вы не можете определить, перекрывается ли этот элемент другим содержимым страницы (это называется occlusion ), или изменяется ли элемент с помощью CSS, например, transform , opacity или filter , что может сделать его невидимым.

Для элемента в документе верхнего уровня эту информацию можно получить, проанализировав DOM с помощью JavaScript, например, с помощью DocumentOrShadowRoot.elementFromPoint() . Однако эту же информацию невозможно получить, если рассматриваемый элемент находится в стороннем iframe.

Почему важна видимость?

К сожалению, в интернете есть недобросовестные игроки. Например, недобросовестный издатель может размещать на сайте рекламу с оплатой за клик. Он может обманным путём заставлять пользователей нажимать на эту рекламу, чтобы заработать больше денег, по крайней мере, до тех пор, пока рекламная сеть не раскроет их мошенничество. Обычно такая реклама размещается в iframe-ах.

Чтобы обмануть пользователей, издатель может сделать рекламные iframe полностью прозрачными с помощью CSS: iframe { opacity: 0; } . Затем он может разместить эти прозрачные iframe поверх привлекательного контента, например, видео с милым котиком, на который пользователи захотят кликнуть. Это называется кликджекингом .

Вы можете увидеть такую ​​атаку кликджекинга в действии в верхней части нашей демонстрации . Попробуйте «посмотреть» видео с котом и активировать режим «Trick Mode» . Реклама в iframe регистрирует клики как легитимные, даже если вы (непреднамеренно) нажали на неё, когда iframe был прозрачным.

Заставить пользователя нажать на объявление, сделав его прозрачным и наложив поверх чего-то привлекательного.

Улучшения в Intersection Observer v2

Intersection Observer v2 может отслеживать «видимость» элемента, как это определяет человек. Если задать соответствующий параметр в конструкторе IntersectionObserver , результирующие экземпляры IntersectionObserverEntry будут включать новое логическое поле isVisible . Если isVisible равно true , браузер гарантирует, что элемент полностью не перекрыт другим содержимым и не имеет визуальных эффектов, скрывающих или изменяющих его отображение. Если isVisible равно false , браузер не может гарантировать этого.

Спецификация допускает ложные отрицания : isVisible может иметь значение false даже если элемент действительно виден и не изменён. Для повышения производительности браузеры используют более простые вычисления, такие как ограничивающие рамки и прямоугольные формы, и не проверяют каждый пиксель на наличие сложных параметров, таких как border-radius .

Однако ложные срабатывания не допускаются ни при каких обстоятельствах. Это означает, что isVisible не будет иметь true если элемент не полностью видим и не изменён.

Применить эти изменения

Конструктор IntersectionObserver теперь принимает два дополнительных свойства конфигурации:

  • delay — число, которое указывает минимальную задержку в миллисекундах между уведомлениями от наблюдателя для заданной цели.
  • trackVisibility — логическое значение, указывающее, будет ли наблюдатель отслеживать изменения видимости цели.

Если trackVisibility имеет true , delay должна быть установлена ​​на значение 100 или выше (то есть не более одного уведомления каждые 100 мс). Поскольку вычисление видимости требует больших затрат, это является мерой предосторожности против снижения производительности и расхода заряда батареи. Ответственным разработчикам следует использовать максимально допустимое значение задержки.

Спецификация рассчитывает видимость. Как и в версии 1, если атрибут trackVisibility наблюдателя равен false , цель считается видимой.

В версии 2 цель считается невидимой, если:

  • Он имеет эффективную матрицу преобразования, отличную от двумерного переноса или пропорционального двумерного масштабирования.

  • Цель или любой элемент в содержащей ее цепочке блоков имеет эффективную непрозрачность меньше 1,0.

  • К цели или любому элементу в содержащей ее цепочке блоков применены какие-либо фильтры.

  • Если реализация не может гарантировать, что цель полностью не перекрыта другим содержимым страницы.

Это означает, что текущие реализации довольно консервативны в плане обеспечения видимости. Например, применение практически незаметного фильтра оттенков серого ( filter: grayscale(0.01%) ) или установка минимальной прозрачности ( opacity: 0.99 ) сделает элемент невидимым.

Вот пример кода, иллюстрирующий новые функции API. Вы можете увидеть логику отслеживания кликов в действии во втором разделе демонстрации . Попробуйте «посмотреть» видео со щенком. Активируйте режим трюка , чтобы стать злодеем, и посмотрите, как Intersection Observer v2 предотвращает отслеживание нелегитимных кликов по рекламе. Intersection Observer v2 защищает нас.

Intersection Observer v2 предотвращает непреднамеренные нажатия на рекламу.

<!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 v2, fallback 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'));

Дополнительные ресурсы

Благодарности

Благодарим Симеона Винсента , Йоава Вайса и Матиаса Байненса за обзор, а также Стефана Загера за обзор и реализацию функции в Chrome.