A modo de resumen
Te cuento un secreto: es posible que no necesites eventos scroll
en tu próxima app. Con un IntersectionObserver
, te mostraré cómo puedes activar un evento personalizado cuando los elementos position:sticky
se fijan o dejan de pegarse. Todo sin el uso de objetos de escucha de desplazamiento. Incluso hay una demostración increíble para probarlo:
Presentamos el evento sticky-change
Una de las limitaciones prácticas de usar la posición fija de CSS es que no proporciona un indicador de plataforma para saber cuándo la propiedad está activa. En otras palabras, no hay un evento para saber cuándo un elemento se vuelve fijo o cuando deja de serlo.
Considera el siguiente ejemplo, que fija un ¿No sería bueno que el navegador indicara cuándo los elementos alcanzan esa marca?
Al parecer, no soy el único
que lo piensa. Un indicador para Con estos casos de uso en mente, creamos un objetivo final: crear un evento que se active cuando un elemento La demo usa este evento para aplicar una sombra a los encabezados cuando se fijan. También se actualiza el
título nuevo en la parte superior de la página. Veamos algunos términos para que pueda hacer referencia a estos nombres
durante el resto de la publicación: Para saber qué encabezado entra en el “modo fijo”, necesitamos una forma de determinar el desplazamiento del contenedor de desplazamiento. Eso nos daría una forma de calcular el encabezado que se muestra actualmente. Sin embargo, eso es bastante difícil de hacer sin eventos Por lo tanto, sin eventos de desplazamiento, perdimos la capacidad de realizar cálculos relacionados con el diseño en los encabezados. En lugar de eventos Necesitamos dos centinelas para cubrir cuatro casos de desplazamiento hacia arriba y hacia abajo: Es útil ver una presentación en pantalla de los pasos del 1 al 4 en el orden en que ocurren: Los centinelas se colocan en la parte superior e inferior de cada sección.
Los observadores de intersección observan de forma asíncrona los cambios en la intersección de un elemento de destino y la vista del documento o un contenedor superior. En nuestro caso, estamos observando intersecciones con un contenedor superior.
La salsa mágica es Primero, configuré observadores para los centinelas de encabezado y pie de página: Luego, agregué un observador para que se active cuando los elementos El observador se configura con El proceso es similar para el centinela inferior ( El observador se configura con Por último, tengo dos utilidades para activar el evento personalizado Eso es todo. Creamos un evento personalizado cuando los elementos con A menudo, me he preguntado si En realidad, no. Lo que necesitábamos era una forma de observar los cambios de estilo en un elemento DOM.
Lamentablemente, no hay nada en las APIs de la plataforma web que te permita observar los cambios de estilo. Una En el futuro, una extensión "Style Mutation Observer" para los observadores de mutaciones podría ser útil para observar los cambios en los diseños calculados de un elemento.
Salvo que se indique lo contrario, el contenido de esta página está sujeto a la licencia Atribución 4.0 de Creative Commons, y los ejemplos de código están sujetos a la licencia Apache 2.0. Para obtener más información, consulta las políticas del sitio de Google Developers. Java es una marca registrada de Oracle o sus afiliados. Última actualización: 2017-09-19 (UTC).sticky {
position: sticky;
top: 10px;
}
position:sticky
podría desbloquear una serie de casos de uso:
position:sticky
se fije. Llamemos al evento sticky-change
:document.addEventListener('sticky-change', e => {
const header = e.detail.target; // header became sticky or stopped sticking.
const sticking = e.detail.stuck; // true when header is sticky.
header.classList.toggle('shadow', sticking); // add drop shadow when sticking.
document.querySelector('.who-is-sticking').textContent = header.textContent;
});
¿Efectos de desplazamiento sin eventos de desplazamiento?
position:sticky
.position:sticky
se aplica al elemento.scroll
. El otro problema es que position:sticky
quita el elemento del diseño cuando se fija.Cómo agregar un DOM simulado para determinar la posición de desplazamiento
scroll
, usaremos un IntersectionObserver
para determinar cuándo los encabezados entran y salen del modo fijo. Si agregas dos nodos (también conocidos como centinelas) en cada sección fija, uno en la parte superior y otro en la parte inferior, estos actuarán como puntos de referencia para determinar la posición de desplazamiento. A medida que estos marcadores entran y salen del contenedor, su visibilidad cambia y el observador de intersecciones activa una devolución de llamada.
El CSS
.sticky_sentinel--top
se encuentra en la parte superior del encabezado, mientras que .sticky_sentinel--bottom
se encuentra en la parte inferior de la sección:
:root {
--default-padding: 16px;
--header-height: 80px;
}
.sticky {
position: sticky;
top: 10px; /* adjust sentinel height/positioning based on this position. */
height: var(--header-height);
padding: 0 var(--default-padding);
}
.sticky_sentinel {
position: absolute;
left: 0;
right: 0; /* needs dimensions */
visibility: hidden;
}
.sticky_sentinel--top {
/* Adjust the height and top values based on your on your sticky top position.
e.g. make the height bigger and adjust the top so observeHeaders()'s
IntersectionObserver fires as soon as the bottom of the sentinel crosses the
top of the intersection container. */
height: 40px;
top: -24px;
}
.sticky_sentinel--bottom {
/* Height should match the top of the header when it's at the bottom of the
intersection container. */
height: calc(var(--header-height) + var(--default-padding));
bottom: 0;
}
Cómo configurar los observadores de intersección
IntersectionObserver
. Cada centinela obtiene un IntersectionObserver
para observar su visibilidad de intersección dentro del contenedor de desplazamiento. Cuando un centinela se desplaza hasta la vista del viewport visible, sabemos que un encabezado se volvió fijo o dejó de serlo. Del mismo modo, cuando un centinela sale del viewport./**
* Notifies when elements w/ the `sticky` class begin to stick or stop sticking.
* Note: the elements should be children of `container`.
* @param {!Element} container
*/
function observeStickyHeaderChanges(container) {
observeHeaders(container);
observeFooters(container);
}
observeStickyHeaderChanges(document.querySelector('#scroll-container'));
.sticky_sentinel--top
pasen por la parte superior del contenedor de desplazamiento (en cualquier dirección).
La función observeHeaders
crea los centinelas superiores y los agrega a cada sección. El observador calcula la intersección del centinela con la parte superior del contenedor y decide si está entrando o saliendo del viewport. Esa información determina si el encabezado de la sección se queda fijo o no./**
* Sets up an intersection observer to notify when elements with the class
* `.sticky_sentinel--top` become visible/invisible at the top of the container.
* @param {!Element} container
*/
function observeHeaders(container) {
const observer = new IntersectionObserver((records, observer) => {
for (const record of records) {
const targetInfo = record.boundingClientRect;
const stickyTarget = record.target.parentElement.querySelector('.sticky');
const rootBoundsInfo = record.rootBounds;
// Started sticking.
if (targetInfo.bottom < rootBoundsInfo.top) {
fireEvent(true, stickyTarget);
}
// Stopped sticking.
if (targetInfo.bottom >= rootBoundsInfo.top &&
targetInfo.bottom < rootBoundsInfo.bottom) {
fireEvent(false, stickyTarget);
}
}
}, {threshold: [0], root: container});
// Add the top sentinels to each section and attach an observer.
const sentinels = addSentinels(container, 'sticky_sentinel--top');
sentinels.forEach(el => observer.observe(el));
}
threshold: [0]
para que su devolución de llamada se active en cuanto el centinela se vuelve visible..sticky_sentinel--bottom
). Se crea un segundo observador para que se active cuando los pies de página pasen por la parte inferior del contenedor de desplazamiento. La función observeFooters
crea los nodos centinela y los adjunta a cada sección. El observador calcula la intersección del centinela con la parte inferior del contenedor y decide si está entrando o saliendo. Esa información determina si el encabezado de la sección se fija o no./**
* Sets up an intersection observer to notify when elements with the class
* `.sticky_sentinel--bottom` become visible/invisible at the bottom of the
* container.
* @param {!Element} container
*/
function observeFooters(container) {
const observer = new IntersectionObserver((records, observer) => {
for (const record of records) {
const targetInfo = record.boundingClientRect;
const stickyTarget = record.target.parentElement.querySelector('.sticky');
const rootBoundsInfo = record.rootBounds;
const ratio = record.intersectionRatio;
// Started sticking.
if (targetInfo.bottom > rootBoundsInfo.top && ratio === 1) {
fireEvent(true, stickyTarget);
}
// Stopped sticking.
if (targetInfo.top < rootBoundsInfo.top &&
targetInfo.bottom < rootBoundsInfo.bottom) {
fireEvent(false, stickyTarget);
}
}
}, {threshold: [1], root: container});
// Add the bottom sentinels to each section and attach an observer.
const sentinels = addSentinels(container, 'sticky_sentinel--bottom');
sentinels.forEach(el => observer.observe(el));
}
threshold: [1]
, por lo que su devolución de llamada se activa cuando todo el nodo está a la vista.sticky-change
y generar los centinelas:/**
* @param {!Element} container
* @param {string} className
*/
function addSentinels(container, className) {
return Array.from(container.querySelectorAll('.sticky')).map(el => {
const sentinel = document.createElement('div');
sentinel.classList.add('sticky_sentinel', className);
return el.parentElement.appendChild(sentinel);
});
}
/**
* Dispatches the `sticky-event` custom event on the target element.
* @param {boolean} stuck True if `target` is sticky.
* @param {!Element} target Element to fire the event on.
*/
function fireEvent(stuck, target) {
const e = new CustomEvent('sticky-change', {detail: {stuck, target}});
document.dispatchEvent(e);
}
Demostración final
position:sticky
se vuelven fijos y agregamos efectos de desplazamiento sin usar eventos scroll
.Conclusión
IntersectionObserver
sería una herramienta útil para reemplazar algunos de los patrones de IU basados en eventos scroll
que se desarrollaron a lo largo de los años. Resulta que la respuesta es sí y no. La semántica
de la API de IntersectionObserver
dificulta su uso para todo. Pero, como te mostré aquí, puedes usarlo para algunas técnicas interesantes.¿Hay otra forma de detectar cambios de estilo?
MutationObserver
sería una primera opción lógica, pero no funciona en la mayoría de los casos. Por ejemplo, en la demostración, recibiríamos una devolución de llamada cuando se agregue la clase sticky
a un elemento, pero no cuando cambie el estilo calculado del elemento.
Recuerda que la clase sticky
ya se declaró cuando se cargó la página.position: sticky
.