IntersectionObservers te informa cuando un elemento observado entra o sale del viewport del navegador.
Supongamos que deseas hacer un seguimiento de cuándo un elemento de tu DOM ingresa al viewport visible. Es posible que quieras hacerlo para que puedas cargar imágenes de forma diferida justo a tiempo o porque necesitas saber si el usuario está mirando un banner de anuncio determinado. Para ello, conecta el evento de desplazamiento o usa un temporizador periódico y llama a getBoundingClientRect()
en ese elemento.
Sin embargo, este enfoque es muy lento, ya que cada llamada a getBoundingClientRect()
obliga al navegador a volver a diseñar toda la página y agrega un bloqueo considerable a tu sitio web. La situación se vuelve casi imposible cuando sabes que tu sitio se carga dentro de un iframe y quieres saber cuándo el usuario puede ver un elemento. El modelo de origen único y el navegador no te permitirán acceder a ningún dato de la página web que contiene el iframe. Este es un problema común de los anuncios, por ejemplo, que se cargan con frecuencia con iframes.
IntersectionObserver
se diseñó para que esta prueba de visibilidad sea más eficiente y está disponible en todos los navegadores modernos. IntersectionObserver
te informa cuando un elemento observado entra o sale del viewport del navegador.
Cómo crear un IntersectionObserver
La API es bastante pequeña y se describe mejor con un ejemplo:
const io = new IntersectionObserver(entries => {
console.log(entries);
}, {
/* Using default options. Details below */
});
// Start observing an element
io.observe(element);
// Stop observing an element
// io.unobserve(element);
// Disable entire IntersectionObserver
// io.disconnect();
Si usas las opciones predeterminadas de IntersectionObserver
, se llamará a tu devolución de llamada cuando el elemento aparezca parcialmente y cuando salga por completo del viewport.
Si necesitas observar varios elementos, es posible y recomendable observar varios elementos con la misma instancia de IntersectionObserver
llamando a observe()
varias veces.
Se pasa un parámetro entries
a tu devolución de llamada, que es un array de objetos IntersectionObserverEntry
. Cada uno de esos objetos contiene datos de intersección actualizados para uno de tus elementos observados.
🔽[IntersectionObserverEntry]
time: 3893.92
🔽rootBounds: ClientRect
bottom: 920
height: 1024
left: 0
right: 1024
top: 0
width: 920
🔽boundingClientRect: ClientRect
// ...
🔽intersectionRect: ClientRect
// ...
intersectionRatio: 0.54
🔽target: div#observee
// ...
rootBounds
es el resultado de llamar a getBoundingClientRect()
en el elemento raíz, que es el viewport de forma predeterminada. boundingClientRect
es el resultado de getBoundingClientRect()
llamado al elemento observado. intersectionRect
es la intersección de estos dos rectángulos y te indica de manera eficaz qué parte del elemento observado es visible. intersectionRatio
está estrechamente relacionado y te indica cuánto del elemento es visible. Con esta información a tu disposición, ahora puedes implementar funciones como la carga just in time de recursos antes de que sean visibles en la pantalla. De forma eficiente.
Los IntersectionObserver
envían sus datos de forma asíncrona, y tu código de devolución de llamada se ejecutará en el subproceso principal. Además, la especificación indica que las implementaciones de IntersectionObserver
deben usar requestIdleCallback()
. Esto significa que la llamada a la devolución de llamada proporcionada tiene prioridad baja y el navegador la realizará durante el tiempo inactivo. Esta es una decisión de diseño consciente.
Divis con desplazamiento
No soy un gran fan del desplazamiento dentro de un elemento, pero no estoy aquí para juzgar, y tampoco lo está IntersectionObserver
. El objeto options
toma una opción root
que te permite definir una alternativa al viewport como raíz. Es importante tener en cuenta que root
debe ser un ancestro de todos los elementos observados.
Intersecciona todo.
¡No! ¡Desarrollador malo! Ese no es un uso consciente de los ciclos de CPU del usuario. Pensemos en un control deslizante infinito como ejemplo: en esa situación, es recomendable agregar sentinelas al DOM y observarlos (¡y reciclarlos!). Debes agregar un centinela cerca del último elemento del control deslizante infinito. Cuando aparezca ese centinela, puedes usar la devolución de llamada para cargar datos, crear los siguientes elementos, adjuntarlos al DOM y cambiar la posición del centinela según corresponda. Si reciclas correctamente el centinela, no se necesita ninguna llamada adicional a observe()
. IntersectionObserver
sigue funcionando.
Más actualizaciones
Como se mencionó anteriormente, la devolución de llamada se activará una sola vez cuando el elemento observado aparezca parcialmente en la vista y otra vez cuando haya salido del viewport. De esta manera, IntersectionObserver
te da una respuesta a la pregunta "¿Está el elemento X en la vista?". Sin embargo, en algunos casos de uso, es posible que eso no sea suficiente.
Aquí es donde entra en juego la opción threshold
. Te permite definir un array de umbrales de intersectionRatio
. Se llamará a tu devolución de llamada cada vez que intersectionRatio
cruce uno de estos valores. El valor predeterminado para threshold
es [0]
, lo que explica el comportamiento predeterminado. Si cambiamos threshold
a [0, 0.25, 0.5, 0.75, 1]
, recibiremos una notificación cada vez que se haga visible un cuarto adicional del elemento:
¿Hay alguna otra opción?
Por el momento, solo hay una opción adicional a las mencionadas anteriormente. rootMargin
te permite especificar los márgenes de la raíz, lo que te permite aumentar o disminuir el área utilizada para las intersecciones. Estos márgenes se especifican con una cadena de estilo CSS, al estilo de "10px 20px 30px 40px"
, que especifica el margen superior, derecho, inferior e izquierdo, respectivamente. En resumen, la estructura de opciones IntersectionObserver
ofrece las siguientes opciones:
new IntersectionObserver(entries => {/* … */}, {
// The root to use for intersection.
// If not provided, use the top-level document's viewport.
root: null,
// Same as margin, can be 1, 2, 3 or 4 components, possibly negative lengths.
// If an explicit root element is specified, components may be percentages of the
// root element size. If no explicit root element is specified, using a
// percentage is an error.
rootMargin: "0px",
// Threshold(s) at which to trigger callback, specified as a ratio, or list of
// ratios, of (visible area / total area) of the observed element (hence all
// entries must be in the range [0, 1]). Callback will be invoked when the
// visible ratio of the observed element crosses a threshold in the list.
threshold: [0],
});
Comando mágico <iframe>
Los IntersectionObserver
se diseñaron específicamente teniendo en cuenta los servicios de anuncios y los widgets de redes sociales, que suelen usar elementos <iframe>
y podrían beneficiarse de saber si están a la vista. Si un <iframe>
observa uno de sus elementos, el desplazamiento del <iframe>
y el desplazamiento de la ventana que contiene el <iframe>
activarán la devolución de llamada en los momentos adecuados. Sin embargo, en el último caso, rootBounds
se establecerá en null
para evitar la filtración de datos entre los orígenes.
¿De qué se trata IntersectionObserver
No?
Ten en cuenta que IntersectionObserver
no es intencionalmente ni píxel perfecto ni de baja latencia. Si los usas para implementar iniciativas como animaciones dependientes del desplazamiento, es probable que falle, ya que los datos estarán, estrictamente hablando, desactualizados para cuando los uses. En la explicación, encontrarás más detalles sobre los casos de uso originales de IntersectionObserver
.
¿Qué puedo hacer durante la devolución de llamada?
Breve y conciso: Si pasas demasiado tiempo en la devolución de llamada, tu app tendrá retrasos. Se aplican todas las prácticas comunes.
Continúa y cruza tus elementos
La compatibilidad del navegador con IntersectionObserver
es buena, ya que está disponible en todos los navegadores modernos. Si es necesario, se puede usar un polyfill en navegadores más antiguos, que está disponible en el repositorio de WICG. Obviamente, no obtendrás los beneficios de rendimiento que te brindaría una implementación nativa con ese polyfill.
Puedes comenzar a usar IntersectionObserver
ahora mismo. Cuéntanos qué ideaste.