Esta traducción está incompleta. Por favor, ayuda a traducir este artículo del inglés.

Esta es una tecnología experimental
Comprueba la Tabla de compabilidad de navegadores cuidadosamente antes de usarla en producción.

La API Observador de Intersección, provee una vía para, de forma asíncrona, observar cambios en la intersección de un elemento objetivo, con un elemento ancestro o con un documento de nivel superior. viewport.

Historicamente, detectar la visibilidad de un elemento, o la visibilidad relativa de dos elementos el uno respecto del otro, ha sido una tarea difícil para la cual las soluciones no han sido muy fiables y propensas a causar que el navegador y los sitios a los que el usuario está accediendo lleguen a ser lentos. Desafortunadamente, a medida que la web ha madurado, la necesidad para este tipo de información ha ido en aumento. La información sobre intersección es necesaria por muchas razaones, tales como:

  • Lazy-loading de imágenes u otro contenido a medida que la página se desplaza.
  • Implementación de "scroll infinito" de sitios web, donde más y más contenido se carga y muestra a medida que usted hace scroll, de forma que el usario no tiene que pasar páginas.
  • Informes de visualizaciones de anuncios para calcular ingresos por publicidad.
  • Decidir si deben realizarse tareas o procesos de animación basados en si el usuario verá o no el resultado.

Imprlementar la detección de intersecciones en el pasado implicaba manejadores de eventos y bucles llamando a métodos como Element.getBoundingClientRect() para construir la información necesaria para cada elemento afectado. Toda vez que este código corre sobre el hilo principal, incluso uno de estos puede causar problemas de rendimiento. Cuando un sitio es cargado con estos tests, las cosas pueden ponerse my feas.

Considere una página web que usa scroll infinito. Usa una librería de terceros para manejar los anuncios situados periódicamente en la página, tiene gráficos animados aquí y allá, y usa una librería personalizada que muestra cajas de notificación y similares. Cada uno de estos tiene sus propias rutinas de detección de intersecciones, todas corriendo en el mismo hilo principal. El autor del sitio web puede no darse cuenta de que esto está pasando, ya que están usando dos librerías de las que quizás conocen muy poco acerca de su funcionamiento interno. A medida que el usuario desplaza la página, estas rutinas de detección de intersecciones están disparando código constantemente durante el scroll, lo que resulta en una experiencia que deja al usuario frustrado con el navegador, el sitio web y su ordenador.

El API Intersection Observer deja al código registrar una funcion callback que se ejecuta si un elemento que se desea monitorizar entra o sale de otro elemento (or the viewport), o cuando la cantidad por la que ambos elementos se intersectan cambia en una cantidad requerida. De esta manera, los sitios no necesitan hacer nada sobre el hilo principal para mirar este tipo de intersección entre elementos, y el navegador está libre para optimizar la gestión de intersecciones como le parezca conveniente.

Una cosa que el API Intersection Observer no puede decirle es: el número exacto de pixels que se solapan o especificamente cuales son; sin embargo, cubre el caso de uso mucho más común de "Si se intersectan por algún lugar alrededor del N%, necesito hacer algo."

Intersection observer conceptos y uso

El API Intersection Observer le permite configurar una función callback que es llamada cada vez que un elemento, llamado target, intersecta con otro, bien el dispositivo viewport o un elemento específico; para el propósito de esta API, le llamaremos root elementroot. Típicamente, usted querrá observar los cambios en las intersecciones con respecto al viewport del docuento (lo cual se hace especificando null como elemento root ). Tanto si está usted usando el viewport o algún otro elemento como root, el API funciona de la misma manera, ejecutando una función callback que usted le proporciona cuando la visibilidad del elemento target cambia al cruzar en la cantidad intersección deseada con el elemento root.

El grado de intersección entre el elemento target y su elemento root es el intersection ratio. Esto es una representación del porcentaje del elemento target que es visible, y se muestra como un valor entre 0.0 y 1.0.

Creando un intersection observer

Crear el intersection observer llamando a su constructor y pasándole una función callback para que se ejecute cuando un nivel (threshold) sea cruzado en una u otra dirección:

var options = {
  root: document.querySelector('#scrollArea'),
  rootMargin: '0px',
  threshold: 1.0
}

var observer = new IntersectionObserver(callback, options);

Un threshold de 1.0 significa que cuando el 100% del elemento target está visible dentro del elemento especificado por la opción root, la función callback es invocada.

Opciones de Intersection observer

El objeto options pasado al constructor IntersectionObserver() le deja controlar las circunstancias bajo las cuales la función callback del observer es invocada. Tiene los siguientes campos:

root
El elemento que es usado como por ejemplo el viewport para comprobar la visibilidad de elemento target. Debe ser un elemento ascendiente del target. Por defecto se toma el viewport del navegador si no se especifica o si se especifica como null.
rootMargin  
Margen alrededor del elemeto root. Puede tener valores similares a los de CSS margin property, e.g. "10px 20px 30px 40px" (top, right, bottom, left). Los valores pueden ser porcentajes. Este conjunto de valores sirve para aumentar o encoger cada lado del cuadro delimitador del elemento root antes de calcular las intersecciones. Por defecto son todos cero.
threshold
Es un número o un array de números que indican a que porcentaje de visibilidad del elemtento target, la función callback del observer debería ser ejecutada. Si usted quiere que se detecte cuando la visibilidad pasa la marca del 50%, debería usar un valor de 0.5. Si quiere ejecutar la función callback cada vez que la visibilidad pase otro 25%, usted debería especificar el array [0, 0.25, 0.5, 0.75, 1]. El valor por defecto es 0 (lo que significa que tan pronto como un píxel sea visible, la función callback será ejecutada). Un valor de 1.0 significa que el threshold no se considera pasado hasta que todos los píxels sean visibles.

Determinando un elemento para ser observado

Una vez usted ha cread el observer, necesita darle un elemento target para observar:

var target = document.querySelector('#listItem');
observer.observe(target);

Cuando el elemento target encuentra un threshold especificado por el IntersectionObserver, la función callback es invocada. La función callback recibe una lista de objetos IntersectionObserverEntry y el observer:

var callback = function(entries, observer) { 
  entries.forEach(entry => {
    // Each entry describes an intersection change for one observed
    // target element:
    //   entry.boundingClientRect
    //   entry.intersectionRatio
    //   entry.intersectionRect
    //   entry.isIntersecting
    //   entry.rootBounds
    //   entry.target
    //   entry.time
  });
};

Asegurese de que su función callback se ejecute sobre el hilo principal. Debería operar tan rapidamente como sea posible; si alguna cosa necesita  tiempo extra para ser realizda, use Window.requestIdleCallback().

También, note que si especifica la opción root, el elemento target debe ser un descendiente del elemento root.

Como se calcula la intersección

All areas considered by the Intersection Observer API are rectangles; elements which are irregularly shaped are considered as occupying the smallest rectangle which encloses all of the element's parts. Similarly, if the visible portion of an element is not rectangular, the element's intersection rectangle is construed to be the smallest rectangle that contains all the visible portions of the element.

It's useful to understand a bit about how the various properties provided by IntersectionObserverEntry describe an intersection.

The intersection root and root margin

Before we can track the intersection of an element with a container, we need to know what that container is. That container is the intersection root, or root element. This can be either an element in the document which is an ancestor of the element to be observed, or null to use the document's viewport as the container.

The rectangle used as the bounds of the intersection root can be adjusted by setting the root margin, rootMargin, when creating the IntersectionObserver. The values in rootMargin define offsets added to each side of the intersection root's bounding box to create the final intersection root bounds (which are disclosed in IntersectionObserverEntry.rootBounds when the callback is executed).

Thresholds

Rather than reporting every infinitesimal change in how much a target element is visible, the Intersection Observer API uses thresholds. When you create an observer, you can provide one or more numeric values representing percentages of the target element which are visible. Then, the API only reports changes to visibility which cross these thresholds.

For example, if you want to be informed every time a target's visibility passes backward or forward through each 25% mark, you would specify the array [0, 0.25, 0.5, 0.75, 1] as the list of thresholds when creating the observer. You can tell which direction the visibility changed in (that is, whether the element became more visible or less visible) by checking the value of the isIntersecting property on the IntersectionObserverEntry passed into the callback function at the time of the visibility change. If isIntersecting is true, the target element has become at least as visible as the threshold that was passed. If it's false, the target is no longer as visible as the given threshold.

To get a feeling for how thresholds work, try scrolling the box below around. Each colored box within it displays the percentage of itself that's visible in all four of its corners, so you can see these ratios change over time as you scroll the container. Each box has a different set of thresholds:

  • The first box has a threshold for each percentage point of visibility; that is, the IntersectionObserver.thresholds array is [0.00, 0.01, 0.02, ..., 0.99, 1.00].
  • The second box has a single threshold, at the 50% mark.
  • The third box has thresholds every 10% of visibility (0%, 10%, 20%, etc.).
  • The last box has thresholds each 25%.

Intersection change callbacks

When the amount of a target element which is visible within the root element crosses one of the visibility thresholds,

Interfaces

IntersectionObserver
The primary interface for the Intersection Observer API. Provides methods for creating and managing an observer which can watch any number of target elements for the same intersection configuration. Each observer can asynchronously observe changes in the intersection between one or more target elements and a shared ancestor element or with their top-level Document's viewport. The ancestor or viewport is referred to as the root.
IntersectionObserverEntry
Describes the intersection between the target element and its root container at a specific moment of transition. Objects of this type can only be obtained in two ways: as an input to your IntersectionObserver callback, or by calling IntersectionObserver.takeRecords().

A simple example

This simple example causes a target element to change its color and transparency as it becomes more or less visible. At Timing element visibility with the Intersection Observer API, you can find a more extensive example showing how to time how long a set of elements (such as ads) are visible to the user and to react to that information by recording statistics or by updating elements..

HTML

The HTML for this example is very short, with a primary element which is the box that we'll be targeting (with the creative ID "box") and some contents within the box.

<div id="box">
  <div class="vertical">
    Welcome to <strong>The Box!</strong>
  </div>
</div>

CSS

The CSS isn't terribly important for the purposes of this example; it lays out the element and establishes that the background-color and border attributes can participate in CSS transitions, which we'll use to affect the changes to the element as it becomes more or less obscured.

#box {
  background-color: rgba(40, 40, 190, 255);
  border: 4px solid rgb(20, 20, 120);
  transition: background-color 1s, border 1s;
  width: 350px;
  height: 350px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.vertical {
  color: white;
  font: 32px "Arial";
}

.extra {
  width: 350px;
  height: 350px;
  margin-top: 10px;
  border: 4px solid rgb(20, 20, 120);
  text-align: center;
  padding: 20px;
}

JavaScript

Finally, let's take a look at the JavaScript code that uses the Intersection Observer API to make things happen.

Setting up

First, we need to prepare some variables and install the observer.

var numSteps = 20.0;

var boxElement;
var prevRatio = 0.0;
var increasingColor = "rgba(40, 40, 190, ratio)";
var decreasingColor = "rgba(190, 40, 40, ratio)";

// Set things up.

window.addEventListener("load", function(event) {
  boxElement = document.querySelector("#box");

  createObserver();
}, false);

The constants and variables we set up here are:

numSteps
A constant which indicates how many thresholds we want to have between a visibility ratio of 0.0 and 1.0.
prevRatio
This variable will be used to record what the visibility ratio was the last time a threshold was crossed; this will let us figure out whether the target element is becoming more or less visible.
increasingColor
A string defining a color we'll apply to the target element when the visibility ratio is increasing. The word "ratio" in this string will be replaced with the target's current visibility ratio, so that the element not only changes color but also becomes increasingly opaque as it becomes less obscured.
decreasingColor
Similarly, this is a string defining a color we'll apply when the visibility ratio is decreasing.

We call Window.addEventListener() to start listening for the load event; once the page has finished loading, we get a reference to the element with the ID "box" using querySelector(), then call the createObserver() method we'll create in a moment to handle building and installing the intersection observer.

Creating the intersection observer

The createObserver() method is called once page load is complete to handle actually creating the new IntersectionObserver and starting the process of observing the target element.

function createObserver() {
  var observer;

  var options = {
    root: null,
    rootMargin: "0px",
    threshold: buildThresholdList()
  };

  observer = new IntersectionObserver(handleIntersect, options);
  observer.observe(boxElement);
}

This begins by setting up an options object containing the settings for the observer. We want to watch for changes in visibility of the target element relative to the document's viewport, so root is null. We need no margin, so the margin offset, rootMargin, is specified as "0px". This causes the observer to watch for changes in the intersection between the target element's bounds and those of the viewport, without any added (or subtracted) space.

The list of visibility ratio thresholds, threshold, is constructed by the function buildThresholdList(). The threshold list is built programmatically in this example since there are a number of them and the number is intended to be adjustable.

Once options is ready, we create the new observer, calling the IntersectionObserver() constructor, specifying a function to be called when intersection crosses one of our thresholds, handleIntersect(), and our set of options. We then call observe() on the returned observer, passing into it the desired target element.

We could opt to monitor multiple elements for visibility intersection changes with respect to the viewport by calling observer.observe() for each of those elements, if we wanted to do so.

Building the array of threshold ratios

The buildThresholdList() function, which builds the list of thresholds, looks like this:

function buildThresholdList() {
  var thresholds = [];

  for (var i=1.0; i<=numSteps; i++) {
    var ratio = i/numSteps;
    thresholds.push(ratio);
  }

  thresholds.push(0);
  return thresholds;
}

This builds the array of thresholds—each of which is a ratio between 0.0 and 1.0, by pushing the value i/numSteps onto the thresholds array for each integer i between 1 and numSteps. It also pushes 0 to include that value. The result, given the default value of numSteps (20), is the following list of thresholds:

# Ratio # Ratio
1 0.05 11 0.55
2 0.1 12 0.6
3 0.15 13 0.65
4 0.2 14 0.7
5 0.25 15 0.75
6 0.3 16 0.8
7 0.35 17 0.85
8 0.4 18 0.9
9 0.45 19 0.95
10 0.5 20 1.0

We could, of course, hard-code the array of thresholds into our code, and often that's what you'll end up doing. But this example leaves room for adding configuration controls to adjust the granularity, for example.

Handling intersection changes

When the browser detects that the target element (in our case, the one with the ID "box") has been unveiled or obscured such that its visibility ratio crosses one of the thresholds in our list, it calls our handler function, handleIntersect():

function handleIntersect(entries, observer) {
  entries.forEach(function(entry) {
    if (entry.intersectionRatio > prevRatio) {
      entry.target.style.backgroundColor = increasingColor.replace("ratio", entry.intersectionRatio);
    } else {
      entry.target.style.backgroundColor = decreasingColor.replace("ratio", entry.intersectionRatio);
    }

    prevRatio = entry.intersectionRatio;
  });
}

For each IntersectionObserverEntry in the list entries, we look to see if the entry's intersectionRatio is going up; if it is, we set the target's background-color to the string in increasingColor (remember, it's "rgba(40, 40, 190, ratio)"), replaces the word "ratio" with the entry's intersectionRatio. The result: not only does the color get changed, but the transparency of the target element changes, too; as the intersection ratio goes down, the background color's alpha value goes down with it, resulting in an element that's more transparent.

Similarly, if the intersectionRatio is going up, we use the string decreasingColor and replace the word "ratio" in that with the intersectionRatio before setting the target element's background-color.

Finally, in order to track whether the intersection ratio is going up or down, we remember the current ratio in the variable prevRatio.

Result

Below is the resulting content. Scroll this page up and down and notice how the appearance of the box changes as you do so.

There's an even more extensive example at Timing element visibility with the Intersection Observer API.

Specifications

Specification Status Comment
Intersection Observer Working Draft  

Browser compatibility

We're converting our compatibility data into a machine-readable JSON format. This compatibility table still uses the old format, because we haven't yet converted the data it contains. Find out how you can help!

Feature Chrome Edge Firefox (Gecko) Internet Explorer Opera Safari (WebKit)
Basic support 51 15 55 (55)[1][2] Sin soporte 38 WebKit bug 159475
Feature Android Webview Chrome for Android Firefox Mobile (Gecko) Firefox OS IE Mobile Opera Mobile Safari Mobile
Basic support 51 51 55.0 (55)[1][2] Sin soporte Sin soporte 38 WebKit bug 159475

[1] This feature has been implemented since Gecko 53.0 (Firefox 53.0 / Thunderbird 53.0 / SeaMonkey 2.50) behind the preference dom.IntersectionObserver.enabled, which was false by default. Enabled by default beginning in Firefox 55. See error 1243846.

[2] Firefox doesn't currently take the clip-path of ancestor elements into account when computing the visibility of an element within its root. See error 1319140 for the status of this issue.

See also

Etiquetas y colaboradores del documento

Colaboradores en esta página: joanvasa, mdnwebdocs-bot, AshWilliams
Última actualización por: joanvasa,