1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
|
---
title: Intersection Observer API
slug: Web/API/Intersection_Observer_API
translation_of: Web/API/Intersection_Observer_API
---
<p>{{DefaultAPISidebar("Intersection Observer API")}}</p>
<div></div>
<p class="summary"><span class="seoSummary">La API Observador de Intersección provee una vía asíncrona para observar cambios en la intersección de un elemento con un elemento ancestro o con el {{Glossary("viewport")}} del documento de nivel superior.</span></p>
<p>Históricamente, detectar la visibilidad de un elemento, o la visibilidad relativa de dos elementos, 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 accede lleguen a ser lentos. 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 razones, tales como:</p>
<ul>
<li>Carga en diferido de imágenes u otro contenido a medida que la página se desplaza.</li>
<li>Implementación del desplazamiento infinito en sitios web, donde más y más contenido se carga y muestra a medida que se desplaza la página, de forma que el usuario no tiene que pasar páginas.</li>
<li>Informes de visualizaciones de anuncios para calcular ingresos por publicidad.</li>
<li>Decidir si deben realizarse tareas o procesos de animación basados en si el usuario verá o no el resultado.</li>
</ul>
<p>Implementar la detección de intersecciones en el pasado implicaba manejadores de eventos y bucles llamando a métodos como {{domxref("Element.getBoundingClientRect()")}} para reunir la información necesaria para cada elemento afectado. Dado que todo 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 muy feas.</p>
<p>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, que 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 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.</p>
<p>El API Intersection Observer deja al código registrar una función callback que se ejecuta si un elemento que se desea monitorizar entra o sale de otro elemento (o del {{Glossary("viewport")}}), o cuando la cantidad por la que ambos elementos se intersecan 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.</p>
<p>Una cosa que el API Intersection Observer no puede decirle es: el número exacto de pixels que se solapan o específicamente cuales son; sin embargo, cubre el caso de uso mucho más común de "Si se intersecan por algún lugar alrededor del <em>N</em>%, necesito hacer algo."</p>
<h2 id="Intersection_observer_conceptos_y_uso">Intersection observer conceptos y uso</h2>
<p>La API Intersection Observer le permite configurar una función callback que es llamada cuando alguna de las siguientes circunstancias ocurren:</p>
<ul>
<li>Un elemento <strong>target </strong>intersecta ya sea al viewport del dispositivo o un elemento especificado. Ese elemento especificado es llamado el <strong>elemento root</strong> o <strong>root</strong> a los propósitos de la API Intersection Observer.</li>
<li>La primera vez que se pide inicialmente al observador que observe un elemento target.</li>
</ul>
<p>Típicamente, usted querrá observar los cambios en las intersecciones con respecto al ancestro scrollable más cercano al elemento, o, si el elemento no desciende de un ancestro scrollable, al viewport.<br>
Para observar la intersección relativa al elemento root, especifique null;</p>
<p>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 de intersección deseada con el elemento root.</p>
<p>El grado de intersección entre el elemento target y su elemento root es el <strong>intersection ratio</strong>. Esto es una representación del porcentaje del elemento target que es visible, indicado como un valor entre 0.0 y 1.0.</p>
<h3 id="Creando_un_intersection_observer">Creando un intersection observer</h3>
<p>Cree el intersection observer llamando a su constructor y pasándole una función callback para que se ejecute cuando se cruce un umbral (threshold) en una u otra dirección:</p>
<pre class="brush: js notranslate">let options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
let observer = new IntersectionObserver(callback, options);</pre>
<p>Un umbral de 1.0 significa que cuando el 100% del elemento target está visible dentro del elemento especificado por la opción <code>root</code>, la función callback es invocada.</p>
<h4 id="Opciones_de_Intersection_observer">Opciones de Intersection observer</h4>
<p>El objeto <code>options</code> pasado al constructor {{domxref("IntersectionObserver.IntersectionObserver", "IntersectionObserver()")}} le deja controlar las circunstancias bajo las cuales la función callback es invocada. Tiene los siguientes campos:</p>
<dl>
<dt><code>root</code></dt>
<dd>El elemento que es usado como viewport para comprobar la visibilidad de elemento target. Debe ser ancestro de target. Por defecto es el viewport del navegador si no se especifica o si es <code>null</code>.</dd>
<dt><code>rootMargin</code> </dt>
<dd>Margen alrededor del elemeto root. Puede tener valores similares a los de CSS {{cssxref("margin")}} property, e.g. "<code>10px 20px 30px 40px"</code> (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.</dd>
<dt><code>threshold</code></dt>
<dd>Es un número o un array de números que indican a que porcentaje de visibilidad del elemento 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 umbral no se considera pasado hasta que todos los pixels son visibles.</dd>
</dl>
<h4 id="Determinando_un_elemento_para_ser_observado">Determinando un elemento para ser observado</h4>
<p>Una vez usted ha creado el observer, necesita darle un elemento target para observar:</p>
<pre class="brush: js notranslate">var target = document.querySelector('#listItem');
observer.observe(target);
// el callback que indicamos al observador será ejecutado ahora por primera vez
// espera hasta que le asignemos un target a nuestro observador (aún si el target no está actualmente visible)
</pre>
<p>Cuando el elemento target encuentra un threshold especificado por el <code>IntersectionObserver</code>, la función callback es invocada. La función callback recibe una lista de objetos {{domxref("IntersectionObserverEntry")}} y el observer:</p>
<pre class="brush: js notranslate">var callback = function(entries, observer) {
entries.forEach(entry => {
// Cada entry describe un cambio en la intersección para
// un elemento observado
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect
// entry.isIntersecting
// entry.rootBounds
// entry.target
// entry.time
});
};
</pre>
<p>Asegúrese de que su función callback se ejecute sobre el hilo principal. Debería operar tan rápidamente como sea posible; si alguna cosa necesita tiempo extra para ser realizada, use {{domxref("Window.requestIdleCallback()")}}.</p>
<p>También, note que si especifica la opción <code>root</code>, el elemento target debe ser un descendiente del elemento root.</p>
<h3 id="Cómo_se_calcula_la_intersección">Cómo se calcula la intersección</h3>
<p>Todas las áreas consideradas por la API de Intersection Observer son rectángulos; los elementos que su forma es irregular se considera que están ocupando el rectángulo más pequeño que encierra todas las partes del elemento. De forma similar, si la porción visible de un elemento no es un rectángulo, entonces el rectángulo de intersección del elemento se interpreta como el rectángulo más pequeño que contiene todas las partes visibles del elemento.</p>
<p>Es útil entender un poco sobre cómo las diferentes propiedades proporcionadas por {{domxref("IntersectionObserverEntry")}} describe una intersección.</p>
<h4 id="La_intersección_entre_el_elemento_root_y_su_margen">La intersección entre el elemento root y su margen</h4>
<p>Antes de poder realizar un seguimiento de la intersección de un elemento en un contenedor, necesitamos saber qué cuál es contendor. Este contenedor se le conoce como <strong>intersection root</strong>, o <strong>root element</strong>. Este puede ser un elemento del documento, que es ascendiente del elemento observado, o <code>null</code>, que usará el viewport del documento como contenedor.</p>
<p>El rectángulo usado como los límites de la intersección del intersection root pueden ser ajustados configurando la opción <strong>root margin</strong>, <code>rootMargin</code>, cuando creamos el {{domxref("IntersectionObserver")}}. Los valores en <code>rootMargin</code> define los espacios añadidos a cada lado del cuadro delimitador que sirve de contenedor, creando los límites definitivos del contenedor, o intersection root (los cuáles están explicados en {{domxref("IntersectionObserverEntry.rootBounds")}} cuando la función callback es ejecutada).</p>
<h4 id="Umbrales">Umbrales</h4>
<p>En lugar de reportar cada mínimo cambio indicando cómo de visible es el elemento que observamos, la Intersection Observer API usa <strong>umbrales</strong>. Cuando creamos un observable, puedes proporcionar uno o más valores númericos representando, en porcentaje, cuán visible es el elemento observado. Entonces, la API reporta sólo los cambios de visibilidad que cruza este umbral.</p>
<p>Por ejemplo, si te gustaría ser informado cada vez que la visibilidad del elemento pasa, hacia delante o hacía atrás, una marca de un 25%, entonces puedes especificar el array [0, 0.25, 0.5, 0.75, 1] como una lista de umbrales a la hora de crear el observable. Puedes saber incluso en qué dirección la visibilidad ha cambiado (esto es, saber si el elemento ha pasado a ser más o menos visible) comprobando el valor de la propiedad {{domxref("IntersectionObserverEntry.isIntersecting", "isIntersecting")}} disponible en el {{domxref("IntersectionObserverEntry")}} que tienes disponible en la función callback cada vez que la visibilidad cambia. Si <code>isIntersecting</code> es <code>true</code>, el elemento se ha vuelto al menos tan visibile como el umbral que pasó. Si es <code>false</code>, el elemento entonces ha dejado de ser tan visible como el umbral que sobrepasó.</p>
<p>Para entender cómo funciona el concepto de umbral (threshold), pruebe a hacer scroll en el siguiente ejemplo. Cada caja coloreada muestra dentro de ella el porcentaje que tiene visible de cada una de las cuadro esquinas, de forma que podrá ver cómo cambian los porcentajes conforme va haciendo scroll. Cada caja tiene diferentes valores configurado de umbrales.</p>
<ul>
<li>La primera caja tiene un umbral para cada punto del porcentaje de visibilidad posible en el array que se le pasa a {{domxref("IntersectionObserver.thresholds")}}, siendo su valor el array <code>[0.00, 0.01, 0.02, ..., 0.99, 1.00]</code>.</li>
<li>La segunda caja sólo tiene un umbral que se marca en el 50%.</li>
<li>La tercera caja tiene un umbral cada 10% de visibilidad (0%, 10%, 20%, etc.).</li>
<li>La última tiene el umbral cada 25%.</li>
</ul>
<div class="hidden" id="threshold-example">
<pre class="brush: html notranslate"><template id="boxTemplate">
<div class="sampleBox">
<div class="label topLeft"></div>
<div class="label topRight"></div>
<div class="label bottomLeft"></div>
<div class="label bottomRight"></div>
</div>
</template>
<main>
<div class="contents">
<div class="wrapper">
</div>
</div>
</main></pre>
<pre class="brush: css notranslate">.contents {
position: absolute;
width: 700px;
height: 1725px;
}
.wrapper {
position: relative;
top: 600px;
}
.sampleBox {
position: relative;
left: 175px;
width: 150px;
background-color: rgb(245, 170, 140);
border: 2px solid rgb(201, 126, 17);
padding: 4px;
margin-bottom: 6px;
}
#box1 {
height: 200px;
}
#box2 {
height: 75px;
}
#box3 {
height: 150px;
}
#box4 {
height: 100px;
}
.label {
font: 14px "Open Sans", "Arial", sans-serif;
position: absolute;
margin: 0;
background-color: rgba(255, 255, 255, 0.7);
border: 1px solid rgba(0, 0, 0, 0.7);
width: 3em;
height: 18px;
padding: 2px;
text-align: center;
}
.topLeft {
left: 2px;
top: 2px;
}
.topRight {
right: 2px;
top: 2px;
}
.bottomLeft {
bottom: 2px;
left: 2px;
}
.bottomRight {
bottom: 2px;
right: 2px;
}
</pre>
<pre class="brush: js notranslate">let observers = [];
startup();
function startup() {
let wrapper = document.querySelector(".wrapper");
// Options for the observers
let observerOptions = {
root: null,
rootMargin: "0px",
threshold: []
};
// Un array con los umbrales para cada caje.
// El umbral de la primer caja se crea de forma programática
// ya que hay demasiados puntos.
let thresholdSets = [
[],
[0.5],
[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0],
[0, 0.25, 0.5, 0.75, 1.0]
];
for (let i=0; i<=1.0; i+= 0.01) {
thresholdSets[0].push(i);
}
// Añadimos cada caja y creamos un observable para cada una
for (let i=0; i<4; i++) {
let template = document.querySelector("#boxTemplate").content.cloneNode(true);
let boxID = "box" + (i+1);
template.querySelector(".sampleBox").id = boxID;
wrapper.appendChild(document.importNode(template, true));
// Configuramos el observable para esta caja
observerOptions.threshold = thresholdSets[i];
observers[i] = new IntersectionObserver(intersectionCallback, observerOptions);
observers[i].observe(document.querySelector("#" + boxID));
}
// Scroll a la posición inicial
document.scrollingElement.scrollTop = wrapper.firstChild.getBoundingClientRect().top + window.scrollY;
document.scrollingElement.scrollLeft = 750;
}
function intersectionCallback(entries) {
entries.forEach(function(entry) {
let box = entry.target;
let visiblePct = (Math.floor(entry.intersectionRatio * 100)) + "%";
box.querySelector(".topLeft").innerHTML = visiblePct;
box.querySelector(".topRight").innerHTML = visiblePct;
box.querySelector(".bottomLeft").innerHTML = visiblePct;
box.querySelector(".bottomRight").innerHTML = visiblePct;
});
}
</pre>
</div>
<p>{{EmbedLiveSample("threshold-example", 500, 500)}}</p>
<h4 id="Acotamiento_y_el_rectángulo_de_intersección">Acotamiento y el rectángulo de intersección</h4>
<p>El navegador computa el rectángulo de intersección final de la siguiente forma; la API hace todo esto por usted, pero puede ser útil entender estos pasos para comprender mejor cuando ocurrirán exactamente las intersecciones.</p>
<ol>
<li>El rectangulo delimitador del elemento target (el rectangulo mas pequeño, que encierra por completo los componentes que conforman el elemento) es obtenido llamando {{domxref("Element.getBoundingClientRect", "getBoundingClientRect()")}} en el target. Este es el rectangulo de intersección mas grande que puede ser. Los pasos restantes removeran las porciones que no intersectan.</li>
<li>Starting at the target's immediate parent block and moving outward, each containing block's clipping (if any) is applied to the intersection rectangle. A block's clipping is determined based on the intersection of the two blocks and the clipping mode (if any) specified by the {{cssxref("overflow")}} property. Setting <code>overflow</code> to anything but <code>visible</code> causes clipping to occur.</li>
<li>If one of the containing elements is the root of a nested browsing context (such as the document contained in an {{HTMLElement("iframe")}}, the intersection rectangle is clipped to the containing context's viewport, and recursion upward through the containers continues with the container's containing block. So if the top level of an <code><iframe></code> is reached, the intersection rectangle is clipped to the frame's viewport, then the frame's parent element is the next block recursed through toward the intersection root.</li>
<li>When recursion upward reaches the intersection root, the resulting rectangle is mapped to the intersection root's coordinate space.</li>
<li>The resulting rectangle is then updated by intersecting it with the <a href="/en-US/docs/Web/API/Intersection_Observer_API#root-intersection-rectangle">root intersection rectangle</a>.</li>
<li>This rectangle is, finally, mapped to the coordinate space of the target's {{domxref("document")}}.</li>
</ol>
<h3 id="Callbacks_de_cambio_de_intersección">Callbacks de cambio de intersección</h3>
<p>Cuando la cantidad del elemento target que es visible dentro del elemento root cruza uno de los umbrales de visibilidad, el callback del objeto {{domxref("IntersectionObserver")}} es ejecutado. El callback recibe como input un array de todos los objetos {{domxref("IntersectionObserverEntry")}}, uno por cada umbral que haya sido cruzado, y una referencia al objeto <code>IntersectionObserver</code> mismo.</p>
<p>Cada entrada en la lista de umbrales es un objeto {{domxref("IntersectionObserverEntry")}} que describe un umbral que ha sido cruzado; esto es, cada entrada describe qué porción de un elemento dado se está intersectando con el elemento root, sea que el elemento se considere en intersección o no, y la dirección en la cual ocurrió la transición.</p>
<p>The code snippet below shows a callback which keeps a counter of how many times elements transition from not intersecting the root to intersecting by at least 75%. For a threshold value of 0.0 (default) the callback is called <a href="https://www.w3.org/TR/intersection-observer/#dom-intersectionobserverentry-isintersecting">approximately</a> upon transition of the boolean value of {{domxref("IntersectionObserverEntry.isIntersecting", "isIntersecting")}}. The snippet thus first checks that the transition is a positive one, then determines whether {{domxref("IntersectionObserverEntry.intersectionRatio", "intersectionRatio")}} is above 75%, in which case it increments the counter.</p>
<pre class="notranslate">intersectionCallback(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
let elem = entry.target;
if (entry.intersectionRatio >= 0.75) {
intersectionCounter++;
}
}
});
}
</pre>
<h2 id="Interfaces">Interfaces</h2>
<dl>
<dt>{{domxref("IntersectionObserver")}}</dt>
<dd>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 {{domxref("Document")}}'s {{Glossary('viewport')}}. The ancestor or viewport is referred to as the <strong>root</strong>.</dd>
<dt>{{domxref("IntersectionObserverEntry")}}</dt>
<dd>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 <code>IntersectionObserver</code> callback, or by calling {{domxref("IntersectionObserver.takeRecords()")}}.</dd>
</dl>
<h2 id="Un_ejemplo_sencillo">Un ejemplo sencillo</h2>
<p>Este ejemplo causa que el elemento que queremos observar cambia de color y transparencia conforme se va haciendo más o menos visible. En la página <a href="/en-US/docs/Web/API/Intersection_Observer_API/Timing_element_visibility">Timing element visibility with the Intersection Observer API</a>, puedes encontrar un ejemplo más extenso que muestra cómo calcular cuanto tiempo que una serie de elementos, como anuncios, son visibles para el usuario y reaccionar a esa información guardando estadísticas.</p>
<h3 id="HTML">HTML</h3>
<p>El HTML para este ejemplo es muy simple, con un elemento primario que será la caja que querremos observar (con la creativa ID de <code>"box"</code>) y algo de contenido para dentro de la caja.</p>
<pre class="brush: html notranslate"><div id="box">
<div class="vertical">
Welcome to <strong>The Box!</strong>
</div>
</div></pre>
<h3 id="CSS">CSS</h3>
<p>El CSS del ejemplo no es muy importante para el propósito de este ejemplo: pinta el elemento y establece que los atributos {{cssxref("background-color")}} y {{cssxref("border")}} puedan participar en las <a href="/en-US/docs/Web/CSS/CSS_Transitions">CSS transitions</a>, los cuáles usaremos para afectar los cambios al elemento conforme este es más o menos visible.</p>
<pre class="brush: css notranslate">#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;
}</pre>
<h3 id="JavaScript">JavaScript</h3>
<p>Finalmente, vamos a mirar el código JavaScript que usa la API Intersection Observer API para hacer que las cosas ocurran.</p>
<h4 id="Preparación">Preparación</h4>
<p>Primero, necesitamos preparar algunas variables e instalar el observador.</p>
<pre class="brush: js notranslate">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);</pre>
<p>Las constantes y variables que establecimos aquí son:</p>
<dl>
<dt><code>numSteps</code></dt>
<dd>A constant which indicates how many thresholds we want to have between a visibility ratio of 0.0 and 1.0.</dd>
<dt><code>prevRatio</code></dt>
<dd>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.</dd>
<dt><code>increasingColor</code></dt>
<dd>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.</dd>
<dt><code>decreasingColor</code></dt>
<dd>Similarly, this is a string defining a color we'll apply when the visibility ratio is decreasing.</dd>
</dl>
<p>We call {{domxref("EventTarget.addEventListener", "Window.addEventListener()")}} to start listening for the {{event("load")}} event; once the page has finished loading, we get a reference to the element with the ID <code>"box"</code> using {{domxref("Document.querySelector", "querySelector()")}}, then call the <code>createObserver()</code> method we'll create in a moment to handle building and installing the intersection observer.</p>
<h4 id="Creating_the_intersection_observer">Creating the intersection observer</h4>
<p>The <code>createObserver()</code> method is called once page load is complete to handle actually creating the new {{domxref("IntersectionObserver")}} and starting the process of observing the target element.</p>
<pre class="brush: js notranslate">function createObserver() {
var observer;
var options = {
root: null,
rootMargin: "0px",
threshold: buildThresholdList()
};
observer = new IntersectionObserver(handleIntersect, options);
observer.observe(boxElement);
}</pre>
<p>This begins by setting up an <code>options</code> 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 <code>root</code> is <code>null</code>. We need no margin, so the margin offset, <code>rootMargin</code>, 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.</p>
<p>The list of visibility ratio thresholds, <code>threshold</code>, is constructed by the function <code>buildThresholdList()</code>. The threshold list is built programmatically in this example since there are a number of them and the number is intended to be adjustable.</p>
<p>Once <code>options</code> is ready, we create the new observer, calling the {{domxref("IntersectionObserver.IntersectionObserver", "IntersectionObserver()")}} constructor, specifying a function to be called when intersection crosses one of our thresholds, <code>handleIntersect()</code>, and our set of options. We then call {{domxref("IntersectionObserver.observe", "observe()")}} on the returned observer, passing into it the desired target element.</p>
<p>We could opt to monitor multiple elements for visibility intersection changes with respect to the viewport by calling <code>observer.observe()</code> for each of those elements, if we wanted to do so.</p>
<h4 id="Building_the_array_of_threshold_ratios">Building the array of threshold ratios</h4>
<p>The <code>buildThresholdList()</code> function, which builds the list of thresholds, looks like this:</p>
<pre class="brush: js notranslate">function buildThresholdList() {
var thresholds = [];
for (var i=1.0; i<=numSteps; i++) {
var ratio = i/numSteps;
thresholds.push(ratio);
}
thresholds.push(0);
return thresholds;
}</pre>
<p>This builds the array of thresholds—each of which is a ratio between 0.0 and 1.0, by pushing the value <code>i/numSteps</code> onto the <code>thresholds</code> array for each integer <code>i</code> between 1 and <code>numSteps</code>. It also pushes 0 to include that value. The result, given the default value of <code>numSteps</code> (20), is the following list of thresholds:</p>
<table class="standard-table">
<tbody>
<tr>
<th>#</th>
<th>Ratio</th>
<th>#</th>
<th>Ratio</th>
</tr>
<tr>
<th>1</th>
<td>0.05</td>
<th>11</th>
<td>0.55</td>
</tr>
<tr>
<th>2</th>
<td>0.1</td>
<th>12</th>
<td>0.6</td>
</tr>
<tr>
<th>3</th>
<td>0.15</td>
<th>13</th>
<td>0.65</td>
</tr>
<tr>
<th>4</th>
<td>0.2</td>
<th>14</th>
<td>0.7</td>
</tr>
<tr>
<th>5</th>
<td>0.25</td>
<th>15</th>
<td>0.75</td>
</tr>
<tr>
<th>6</th>
<td>0.3</td>
<th>16</th>
<td>0.8</td>
</tr>
<tr>
<th>7</th>
<td>0.35</td>
<th>17</th>
<td>0.85</td>
</tr>
<tr>
<th>8</th>
<td>0.4</td>
<th>18</th>
<td>0.9</td>
</tr>
<tr>
<th>9</th>
<td>0.45</td>
<th>19</th>
<td>0.95</td>
</tr>
<tr>
<th>10</th>
<td>0.5</td>
<th>20</th>
<td>1.0</td>
</tr>
</tbody>
</table>
<p>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.</p>
<h4 id="Handling_intersection_changes">Handling intersection changes</h4>
<p>When the browser detects that the target element (in our case, the one with the ID <code>"box"</code>) has been unveiled or obscured such that its visibility ratio crosses one of the thresholds in our list, it calls our handler function, <code>handleIntersect()</code>:</p>
<pre class="brush: js notranslate">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;
});
}</pre>
<p>For each {{domxref("IntersectionObserverEntry")}} in the list <code>entries</code>, we look to see if the entry's {{domxref("IntersectionObserverEntry.intersectionRatio", "intersectionRatio")}} is going up; if it is, we set the target's {{cssxref("background-color")}} to the string in <code>increasingColor</code> (remember, it's <code>"rgba(40, 40, 190, ratio)"</code>), replaces the word "ratio" with the entry's <code>intersectionRatio</code>. 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.</p>
<p>Similarly, if the <code>intersectionRatio</code> is going up, we use the string <code>decreasingColor</code> and replace the word "ratio" in that with the <code>intersectionRatio</code> before setting the target element's <code>background-color</code>.</p>
<p>Finally, in order to track whether the intersection ratio is going up or down, we remember the current ratio in the variable <code>prevRatio</code>.</p>
<h3 id="Resultado">Resultado</h3>
<p>Abajo se encuentra el contenido resultante. Desplace ésta página hacia arriba y abajo y note como la apariencia de la caja cambia mientras lo hace.</p>
<p>{{EmbedLiveSample('A_simple_example', 400, 400)}}</p>
<p>Hay un ejemplo aún más extensivo en <a href="/en-US/docs/Web/API/Intersection_Observer_API/Timing_element_visibility">Cronometrando la visibilidad de un elemento con la API Intersection Observer</a>.</p>
<h2 id="Especificaciones">Especificaciones</h2>
<table class="standard-table">
<tbody>
<tr>
<th scope="col">Especificación</th>
<th scope="col">Estado</th>
<th scope="col">Comentario</th>
</tr>
<tr>
<td>{{SpecName('IntersectionObserver')}}</td>
<td>{{Spec2('IntersectionObserver')}}</td>
<td></td>
</tr>
</tbody>
</table>
<h2 id="Compatibilidad_de_navegadores">Compatibilidad de navegadores</h2>
<p>{{CompatibilityTable}}</p>
<div id="compat-desktop">
<table class="compat-table">
<tbody>
<tr>
<th>Feature</th>
<th>Chrome</th>
<th>Edge</th>
<th>Firefox (Gecko)</th>
<th>Internet Explorer</th>
<th>Opera</th>
<th>Safari (WebKit)</th>
</tr>
<tr>
<td>Basic support</td>
<td>{{CompatChrome(51)}}</td>
<td>15</td>
<td>{{CompatGeckoDesktop(55)}}<sup>[1][2]</sup></td>
<td>{{CompatNo}}</td>
<td>{{CompatOpera(38)}}</td>
<td>{{WebKitBug(159475)}}</td>
</tr>
</tbody>
</table>
</div>
<div id="compat-mobile">
<table class="compat-table">
<tbody>
<tr>
<th>Feature</th>
<th>Android Webview</th>
<th>Chrome for Android</th>
<th>Firefox Mobile (Gecko)</th>
<th>Firefox OS</th>
<th>IE Mobile</th>
<th>Opera Mobile</th>
<th>Safari Mobile</th>
</tr>
<tr>
<td>Basic support</td>
<td>{{CompatChrome(51)}}</td>
<td>{{CompatChrome(51)}}</td>
<td>{{CompatGeckoMobile(55)}}<sup>[1][2]</sup></td>
<td>{{CompatNo}}</td>
<td>{{CompatNo}}</td>
<td>{{CompatOperaMobile(38)}}</td>
<td>{{WebKitBug(159475)}}</td>
</tr>
</tbody>
</table>
</div>
<p>[1] This feature has been implemented since Gecko 53.0 {{geckoRelease("53.0")}} behind the preference <code>dom.IntersectionObserver.enabled</code>, which was <code>false</code> by default. Enabled by default beginning in Firefox 55. See {{bug(1243846)}}.</p>
<p>[2] Firefox doesn't currently take the {{cssxref("clip-path")}} of ancestor elements into account when computing the visibility of an element within its root. See {{bug(1319140)}} for the status of this issue.</p>
<h2 id="Ver_también">Ver también</h2>
<ul>
<li><a href="https://github.com/w3c/IntersectionObserver">Intersection Observer polyfill</a></li>
<li><a href="/en-US/docs/Web/API/Intersection_Observer_API/Timing_element_visibility">Timing element visibility with the Intersection Observer API</a></li>
<li>{{domxref("IntersectionObserver")}} and {{domxref("IntersectionObserverEntry")}}</li>
</ul>
|