Construyendo un encabezado dinámico con Intersection Observer

 

 

 

  • ¡Registro!
  • Implemente rápidamente. Implementar inteligentemente

  • Índice
    1. Uso básico
      1. rootMargin
      2. threshold
    2. Creando el encabezado
      1. Margen
      2. Advertencia de marco flotante
    3. CSS
    4. Creando el observador
      1. Encontrar la dirección de desplazamiento
    5. Agregar el marcador dinámico
    6. Desplazamiento suave
    7. Demostración final
    8. Soporte del navegador
    9. Recursos

    ¿Alguna vez ha necesitado crear una interfaz de usuario en la que algún componente de la página deba responder a los elementos a medida que se desplazan hasta un cierto umbral dentro de la ventana gráfica, o tal vez dentro y fuera de la ventana gráfica misma? En JavaScript, adjuntar un detector de eventos para activar constantemente una devolución de llamada al desplazarse puede consumir mucho rendimiento y, si se usa de manera imprudente, puede generar una experiencia de usuario lenta. Pero hay una mejor manera con Intersection Observer.

     

    La API Intersection Observer es una API de JavaScript que nos permite observar un elemento y detectar cuándo pasa por un punto específico en un contenedor de desplazamiento (a menudo (pero no siempre) la ventana gráfica), lo que activa una función de devolución de llamada.

    Intersection Observer puede considerarse más eficaz que escuchar eventos de desplazamiento en el hilo principal, ya que es asincrónico y la devolución de llamada solo se activará cuando el elemento que estamos observando alcance el umbral especificado, en lugar de eso, cada vez que se actualice la posición de desplazamiento. En este artículo, veremos un ejemplo de cómo podemos usar Intersection Observer para crear un componente de encabezado fijo que cambia cuando se cruza con diferentes secciones de la página web.

     

    Uso básico

    Para usar Intersection Observer, primero debemos crear un nuevo observador, que toma dos parámetros: un objeto con las opciones del observador y la función de devolución de llamada que queremos ejecutar cada vez que el elemento que estamos observando (conocido como objetivo del observador) se cruza. con la raíz (el contenedor de desplazamiento, que debe ser un antepasado del elemento de destino).

    const options = { root: document.querySelector('[data-scroll-root]'), rootMargin: '0px', threshold: 1.0}const callback = (entries, observer) = { entries.forEach((entry) = console.log(entry))}const observer = new IntersectionObserver(callback, options)

    Cuando hayamos creado nuestro observador, debemos indicarle que observe un elemento objetivo:

    const targetEl = document.querySelector('[data-target]')observer.observe(targetEl)

    Cualquiera de los valores de las opciones se puede omitir, ya que volverán a sus valores predeterminados:

    const options = { rootMargin: '0px', threshold: 1.0}

    Si no se especifica ninguna raíz, se clasificará como ventana gráfica del navegador. El ejemplo de código anterior muestra los valores predeterminados para rootMarginy threshold. Estos pueden ser difíciles de visualizar, por lo que vale la pena explicarlos:

    rootMargin

    El rootMarginvalor es un poco como agregar márgenes CSS al elemento raíz y, al igual que los márgenes, puede tomar múltiples valores, incluidos valores negativos. Se considerará que el elemento objetivo se cruza con respecto a los márgenes.

    La raíz de desplazamiento con valores de margen de raíz positivos y negativos. El cuadrado naranja está colocado en el punto donde se clasificaría como "intersección", suponiendo un valor de umbral predeterminado de 1. ( Vista previa grande )

    Eso significa que un elemento técnicamente puede clasificarse como "que se cruza" incluso cuando no está a la vista (si nuestra raíz de desplazamiento es la ventana gráfica).

    El cuadrado naranja se cruza con la raíz, aunque está fuera del área visible. ( Vista previa grande )

    rootMarginEl valor predeterminado es 0px, pero puede tomar una cadena que consta de varios valores, al igual que usar la marginpropiedad en CSS.

    threshold

    Puede thresholdconsistir en un solo valor o una matriz de valores entre 0 y 1. Representa la proporción del elemento que debe estar dentro de los límites de la raíz para que se considere que se cruza . Usando el valor predeterminado de 1, la devolución de llamada se activará cuando el 100% del elemento objetivo sea visible dentro de la raíz.

    Los umbrales de 1, 0 y 0,5 respectivamente dan como resultado que la devolución de llamada se active cuando el 100 %, 0 % y 50 % son visibles. ( Vista previa grande )

    No siempre es fácil visualizar cuándo un elemento se clasificará como visible usando estas opciones. He creado una pequeña herramienta para ayudar a familiarizarme con Intersection Observer.

    Creando el encabezado

    Ahora que hemos comprendido los principios básicos, comencemos a construir nuestro encabezado dinámico. Comenzaremos con una página web dividida en secciones. Esta imagen muestra el diseño completo de la página que crearemos:

    ( Vista previa grande )

    He incluido una demostración al final de este artículo, así que siéntete libre de acceder directamente a ella si deseas descifrar el código. (También hay un repositorio de Github ).

    Cada sección tiene una altura mínima de 100vh(aunque podrían ser más largas, dependiendo del contenido). Nuestro encabezado está fijo en la parte superior de la página y permanece en su lugar mientras el usuario se desplaza (usando position: fixed). Las secciones tienen fondos de diferentes colores y, cuando se encuentran con el encabezado, los colores del encabezado cambian para complementar los de la sección. También hay un marcador para mostrar la sección actual en la que se encuentra el usuario, que se desliza cuando llega la siguiente sección. Para que nos resulte más fácil llegar directamente al código relevante, configuré una demostración mínima con nuestro punto de partida (antes de comenzar a usar la API de Intersection Observer), en caso de que desee seguirla.

    Margen

    Comenzaremos con el HTML de nuestro encabezado. Este será un encabezado bastante simple con un enlace de inicio y navegación, nada especialmente sofisticado, pero usaremos un par de atributos de datos: data-headerpara el encabezado en sí (para que podamos apuntar al elemento con JS) y tres enlaces de anclaje con el atributo data-link, que desplazará al usuario a la sección relevante cuando haga clic en:

    header data-header nav div a href="#0"Home/a /div ul li a href="#about-us" data-linkAbout us/a /li li a href="#flavours" data-linkThe flavours/a /li li a href="#get-in-touch" data-linkGet in touch/a /li /ul /nav/header

    A continuación, el HTML del resto de nuestra página, que está dividido en secciones. Por brevedad, solo he incluido las partes relevantes para el artículo, pero el marcado completo está incluido en la demostración. Cada sección incluye un atributo de datos que especifica el nombre del color de fondo y uno idque corresponde a uno de los enlaces de anclaje en el encabezado:

    main section data-section="raspberry" !--Section content-- /section section data-section="mint" !--Section content-- /section section data-section="vanilla" !--Section content-- /section section data-section="chocolate" !--Section content-- /section/main

    Colocaremos nuestro encabezado con CSS para que permanezca fijo en la parte superior de la página mientras el usuario se desplaza:

     

    header { position: fixed; width: 100%;}

    También le daremos a nuestras secciones una altura mínima y centraremos el contenido. (Este código no es necesario para que Intersection Observer funcione, es solo para el diseño).

    section { padding: 5rem 0; min-height: 100vh; display: flex; justify-content: center; align-items: center;}

    Advertencia de marco flotante

    Mientras creaba esta demostración de Codepen, me encontré con un problema desconcertante en el que mi código de Intersection Observer, que debería haber funcionado perfectamente, no activaba la devolución de llamada en el punto correcto de la intersección, sino que se activaba cuando el elemento objetivo se cruzaba con el borde de la ventana gráfica. Después de pensar un poco, me di cuenta de que esto se debía a que en Codepen el contenido se carga dentro de un iframe, que se trata de manera diferente. (Consulte la sección de documentos de MDN sobre recorte y rectángulo de intersección para obtener detalles completos).

    Como solución alternativa, en la demostración podemos incluir nuestro marcado en otro elemento, que actuará como contenedor de desplazamiento (la raíz en nuestras opciones de IO) en lugar de la ventana gráfica del navegador, como podríamos esperar:

    div data-scroller header data-header !--Header content-- /header main !--Sections-- /main/div

    Si desea ver cómo usar la ventana gráfica como raíz para la misma demostración, esto está incluido en el repositorio de Github .

    CSS

    En nuestro CSS definiremos algunas propiedades personalizadas para los colores que estamos usando. También definiremos dos propiedades personalizadas adicionales para el texto del encabezado y los colores de fondo, y estableceremos algunos valores iniciales. (Más adelante actualizaremos estas dos propiedades personalizadas para las diferentes secciones).

    :root { --mint: #5ae8d5; --chocolate: #573e31; --raspberry: #f2308e; --vanilla: #faf2c8; --headerText: var(--vanilla); --headerBg: var(--raspberry);}

    Usaremos estas propiedades personalizadas en nuestro encabezado:

    header { background-color: var(--headerBg); color: var(--headerText);}

    También estableceremos los colores para nuestras diferentes secciones. Estoy usando los atributos de datos como selectores, pero también puedes usar una clase si lo prefieres.

    [data-section="raspberry"] { background-color: var(--raspberry); color: var(--vanilla);}[data-section="mint"] { background-color: var(--mint); color: var(--chocolate);}[data-section="vanilla"] { background-color: var(--vanilla); color: var(--chocolate);}[data-section="chocolate"] { background-color: var(--chocolate); color: var(--vanilla);}

    También podemos establecer algunos estilos para nuestro encabezado cuando cada sección esté a la vista: Korean Beauty

    /* Header */[data-theme="raspberry"] { --headerText: var(--raspberry); --headerBg: var(--vanilla);}[data-theme="mint"] { --headerText: var(--mint); --headerBg: var(--chocolate);}[data-theme="chocolate"] { --headerText: var(--chocolate); --headerBg: var(--vanilla);}

    Hay un caso más sólido para usar atributos de datos aquí porque vamos a alternar el data-themeatributo del encabezado en cada intersección.

     

    Creando el observador

    Ahora que tenemos el HTML y CSS básicos para nuestra página configurados, podemos crear un observador para observar cada una de nuestras secciones que aparecen. Queremos activar una devolución de llamada cada vez que una sección entre en contacto con la parte inferior del encabezado mientras nos desplazamos hacia abajo en la página. Esto significa que debemos establecer un margen raíz negativo que corresponda a la altura del encabezado.

    const header = document.querySelector('[data-header]')const sections = [...document.querySelectorAll('[data-section]')]const scrollRoot = document.querySelector('[data-scroller]')const options = { root: scrollRoot, rootMargin: `${header.offsetHeight * -1}px`, threshold: 0}

    Estamos estableciendo un umbral de 0 , ya que queremos que se active si alguna parte de la sección se cruza con el margen raíz.

    En primer lugar, crearemos una devolución de llamada para cambiar el data-themevalor del encabezado. (Esto es más sencillo que agregar y eliminar clases, especialmente cuando nuestro elemento de encabezado puede tener aplicadas otras clases).

    /* The callback that will fire on intersection */const onIntersect = (entries) = { entries.forEach((entry) = { const theme = entry.target.dataset.section header.setAttribute('data-theme', theme) })}

    Luego crearemos el observador para observar las secciones que se cruzan:

    /* Create the observer */const observer = new IntersectionObserver(onIntersect, options)/* Set our observer to observe each section */sections.forEach((section) = { observer.observe(section)})

    Ahora deberíamos ver los colores de nuestro encabezado actualizarse cuando cada sección se encuentre con el encabezado.

    Vea el bolígrafo [Heladería Happy Face - Paso 2] (https://codepen.io/smashingmag/pen/poPgpjZ) de Michelle Barker .

    Sin embargo, es posible que notes que los colores no se actualizan correctamente a medida que nos desplazamos hacia abajo. De hecho, ¡el encabezado se actualiza con los colores de la sección anterior cada vez! Desplazarse hacia arriba, en cambio, funciona perfectamente. Necesitamos determinar la dirección de desplazamiento y cambiar el comportamiento en consecuencia.

    Encontrar la dirección de desplazamiento

    Estableceremos una variable en nuestro JS para la dirección de desplazamiento, con un valor inicial de 'up'y otra para la última posición de desplazamiento conocida ( prevYPosition). Luego, dentro del callback, si la posición de desplazamiento es mayor que el valor anterior, podemos establecer el directionvalor como 'down', o 'up'viceversa.

    let direction = 'up'let prevYPosition = 0const setScrollDirection = () = { if (scrollRoot.scrollTop prevYPosition) { direction = 'down' } else { direction = 'up' } prevYPosition = scrollRoot.scrollTop}const onIntersect = (entries, observer) = { entries.forEach((entry) = { setScrollDirection() /* ... */ })}

    También crearemos una nueva función para actualizar los colores del encabezado, pasando la sección de destino como argumento:

     

    const updateColors = (target) = { const theme = target.dataset.section header.setAttribute('data-theme', theme)}const onIntersect = (entries) = { entries.forEach((entry) = { setScrollDirection() updateColors(entry.target) })}

    Hasta ahora no deberíamos ver ningún cambio en el comportamiento de nuestro encabezado. Pero ahora que conocemos la dirección de desplazamiento, podemos pasar un objetivo diferente para nuestra updateColors()función. Si la dirección de desplazamiento es hacia arriba, usaremos el objetivo de entrada. Si está inactivo, usaremos la siguiente sección (si la hay).

    const getTargetSection = (target) = { if (direction === 'up') return target if (target.nextElementSibling) { return target.nextElementSibling } else { return target }}const onIntersect = (entries) = { entries.forEach((entry) = { setScrollDirection() const target = getTargetSection(entry.target) updateColors(target) })}

    Sin embargo, hay un problema más: el encabezado se actualizará no solo cuando la sección llegue al encabezado, sino también cuando el siguiente elemento aparezca en la parte inferior de la ventana gráfica. Esto se debe a que nuestro observador activa la devolución de llamada dos veces: una cuando el elemento entra y otra cuando sale.

    Para determinar si el encabezado debe actualizarse, podemos usar la isIntersectingclave del entryobjeto. Creemos otra función para devolver un valor booleano que indique si los colores del encabezado deben actualizarse:

    const shouldUpdate = (entry) = { if (direction === 'down' !entry.isIntersecting) { return true } if (direction === 'up' entry.isIntersecting) { return true } return false}

    Actualizaremos nuestra onIntersect()función en consecuencia:

    const onIntersect = (entries) = { entries.forEach((entry) = { setScrollDirection() /* Do nothing if no need to update */ if (!shouldUpdate(entry)) return const target = getTargetSection(entry.target) updateColors(target) })}

    Ahora nuestros colores deberían actualizarse correctamente. Podemos configurar una transición CSS para que el efecto sea un poco mejor:

    header { transition: background-color 200ms, color 200ms;}

    Vea el bolígrafo [Heladería Happy Face - Paso 3] (https://codepen.io/smashingmag/pen/bGWEaEa) de Michelle Barker .

    Agregar el marcador dinámico

    A continuación agregaremos un marcador al encabezado que actualiza su posición a medida que nos desplazamos por las diferentes secciones. Podemos usar un pseudoelemento para esto, por lo que no necesitamos agregar nada a nuestro HTML. Le daremos un estilo CSS simple para colocarlo en la parte superior izquierda del encabezado y darle un color de fondo. Estamos usando currentColorpara esto, ya que tomará el valor del color del texto del encabezado:

    header::after { content: ''; position: absolute; top: 0; left: 0; height: 0.4rem; background-color: currentColor;}

    Podemos usar una propiedad personalizada para el ancho, con un valor predeterminado de 0. También usaremos una propiedad personalizada para el valor de traducción x. Vamos a establecer los valores para estos en nuestra función de devolución de llamada a medida que el usuario se desplaza.

     

    header::after { content: ''; position: absolute; top: 0; left: 0; height: 0.4rem; width: var(--markerWidth, 0); background-color: currentColor; transform: translate3d(var(--markerLeft, 0), 0, 0);}

    Ahora podemos escribir una función que actualizará el ancho y la posición del marcador en el punto de intersección:

    const updateMarker = (target) = { const id = target.id /* Do nothing if no target ID */ if (!id) return /* Find the corresponding nav link, or use the first one */ let link = headerLinks.find((el) = { return el.getAttribute('href') === `#${id}` }) link = link || headerLinks[0] /* Get the values and set the custom properties */ const distanceFromLeft = link.getBoundingClientRect().left header.style.setProperty('--markerWidth', `${link.clientWidth}px`) header.style.setProperty('--markerLeft', `${distanceFromLeft}px`)}

    Podemos llamar a la función al mismo tiempo que actualizamos los colores:

    const onIntersect = (entries) = { entries.forEach((entry) = { setScrollDirection() if (!shouldUpdate(entry)) return const target = getTargetSection(entry.target) updateColors(target) updateMarker(target) })}

    También necesitaremos establecer una posición inicial para el marcador, para que no aparezca de la nada. Cuando el documento esté cargado, llamaremos a la updateMarker()función, usando la primera sección como destino:

    document.addEventListener('readystatechange', e = { if (e.target.readyState === 'complete') { updateMarker(sections[0]) }})

    Finalmente, agreguemos una transición CSS para que el marcador se deslice por el encabezado de un enlace al siguiente. A medida que realizamos la transición de la widthpropiedad, podemos usarla will-changepara permitir que el navegador realice optimizaciones .

    header::after { transition: transform 250ms, width 200ms, background-color 200ms; will-change: width;}

    Desplazamiento suave

    Para darle un toque final, sería bueno si, cuando un usuario hace clic en un enlace, se desplazara suavemente hacia abajo en la página, en lugar de saltar a la sección. Hoy en día podemos hacerlo directamente en nuestro CSS, ¡no se requiere JS! Para una experiencia más accesible, es una buena idea respetar las preferencias de movimiento del usuario implementando solo un desplazamiento suave si no ha especificado una preferencia de movimiento reducido en la configuración del sistema:

    @media (prefers-reduced-motion: no-preference) { .scroller { scroll-behavior: smooth; }}

    Demostración final

    La combinación de todos los pasos anteriores da como resultado la demostración completa.

    Vea el bolígrafo [Heladería Happy Face: ejemplo de Intersection Observer] (https://codepen.io/smashingmag/pen/XWRXVXQ) de Michelle Barker .

    Soporte del navegador

    Intersection Observer es ampliamente compatible con los navegadores modernos. Cuando sea necesario, se puede rellenar para navegadores más antiguos, pero prefiero adoptar un enfoque de mejora progresiva siempre que sea posible. En el caso de nuestro encabezado, no sería muy perjudicial para la experiencia del usuario proporcionar una versión simple e inalterable para navegadores no compatibles.

    Para detectar si se admite Intersection Observer, podemos usar lo siguiente:

    if ('IntersectionObserver' in window 'IntersectionObserverEntry' in window 'intersectionRatio' in window.IntersectionObserverEntry.prototype) { /* Code to execute if IO is supported */} else { /* Code to execute if not supported */}

    Recursos

    Lea más sobre Intersection Observer:

    • Amplia documentación, con algunos ejemplos prácticos de MDN .
    • Herramienta de visualización Intersection Observer
    • Visibilidad del elemento de sincronización con la API de Intersection Observer : otro tutorial de MDN que analiza cómo se puede utilizar IO para realizar un seguimiento de la visibilidad de los anuncios.
    • Este artículo de Denys Mishunov cubre algunos otros usos de IO, incluidos los activos de carga diferida. Aunque eso es menos necesario ahora (gracias al loadingatributo), todavía hay mucho que aprender aquí.

    (vf, il)Explora más en

    • interfaz de usuario
    • API
    • javascript
    • Actuación





    Tal vez te puede interesar:

    1. ¿Deberían abrirse los enlaces en ventanas nuevas?
    2. 24 excelentes tutoriales de AJAX
    3. 70 técnicas nuevas y útiles de AJAX y JavaScript
    4. Más de 45 excelentes recursos y repositorios de fragmentos de código

    Construyendo un encabezado dinámico con Intersection Observer

    Construyendo un encabezado dinámico con Intersection Observer

    ¡Registro! Implemente rápidamente. Implementar inteligentemente Índice Uso básico

    programar

    es

    https://aprendeprogramando.es/static/images/programar-construyendo-un-encabezado-dinamico-con-intersection-observer-1110-0.jpg

    2024-04-04

     

    Construyendo un encabezado dinámico con Intersection Observer
    Construyendo un encabezado dinámico con Intersection Observer

    Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente

     

     

    Top 20