soy Kseso y esto EsCSS

Javascript con Furoya: Parallax y Lazy-Load ¿estoy dentro o fuera del viewport?

Segundo artículo dedicado al control y manejo del scroll mediante Javascript. En esta ocasión para saber si un elemento está dentro o fuera del viewport y en función de su posición actuar para aplicar Css o cargar imágenes (lazy load).

Javascript con Furoya: Parallax y Lazy-Load ¿estoy dentro o fuera del viewport?

✎ 5
COLABORACIÓN AUTOR INVITADO
Javascript con Furoya: Lazy load ¿estoy dentro o fuera del viewport?

En el artículo anterior prometí una continuación en el uso de onscroll con fines más prácticos que desaparecer un encabezado. Y no voy a cumplir tal y como quería; pero al menos les voy a contar por qué.

La historia comienza semanas atrás cuando publique un ejemplo que no me pareció muy útil, pero lo pidieron varias veces (y lo siguen haciendo) en distintos foros [ver].

Lo hice porque estaba sin ideas, y creí que luego de verlo funcionando se me iba a ocurrir algo. No pasó. Digo, no inmediatamente. Porque después entendí que el punto fuerte del efecto estaba en modificar la propia imagen.

Así que comencé a trabajar en una galería (¡otra más!) que animara las fotos cuando aparecieran en el documento. La versión es perfectible (y mucho) pero todos entendemos que estos no son ejemplos para sitios "de producción" sino pura y exclusivamente para estudiarlos.

En vez de poner botones o miniaturas a modo de barra, las imágenes se mueven con la vieja, conocida, y poco valorada scrollbar, arrastrando hacia abajo hasta recorrerlas todas. Funciona unicamente en esa dirección, un poco porque es muy molesto re-visar una galería cuando las fotos bailan todo el tiempo (ésta hace la animación sólo una vez al mostrar la imagen), y otro porque no hay necesidad de complicar demasiado el ejemplo.

See the Pen Galería de imágenes vertical con animación. by solipsistaCP (@solipsistacp) on CodePen.

Ver Demo A Full

Como en la entrega anterior, las animaciones son CSS. Javascript solamente lee la posición del documento y la compara con la de cada imagen; si coincide con las reglas y condiciones que aplicamos, le cambia la clase.

El código completo puedes verlo en el pen, así que aquí sólo traigo el script comentado:

<script type="text/javascript"> var cadaImagen, altoViewPort, desplazado, posicionCambio; function carga() { /* COLECCIÓN DE FOTOS EN GALERÍA */ cadaImagen = document.querySelectorAll("#galeria img"); //cadaImagen[0].classList.add("cierra"); /* AGREGA A TODAS (EXCEPTO LA PRIMERA) LA CLASE "cierra" */ for (i=1; i<cadaImagen.length; i++) { cadaImagen[i].classList.add("cierra"); } //setTimeout('cadaImagen[0].classList.remove("cierra"); cadaImagen[0].classList.add("abre")', 1000); } function mueve() { altoViewPort = window.innerHeight; /* DISTANCIA SCROLL */ desplazado = self.pageYOffset || document.documentElement.scrollTop; /* DESPLAZADO + LA MITAD DEL VIEWPORT, PARA QUE LA IMAGEN CAMBIE CUANDO LLEGUE A LA MITAD DE LA VENTANA */ posicionCambio = desplazado + (altoViewPort / 2); /* PRUEBA LA DISTANCIA DE CADA IMAGEN, Y CUANDO ALGUNA LLEGA A LA MITAD SUPERIOR DEL VIEWPORT LE QUITA LA CLASE "cierra" Y AGREGA LA CLASE "abre" */ for(h=0; h<cadaImagen.length; h++){ if(cadaImagen[h].offsetTop < posicionCambio){ cadaImagen[h].classList.remove("cierra"); cadaImagen[h].classList.add("abre"); } } /* PARA AUMENTAR ALTURA DE PIE CUANDO EL SCROLL LLEGUE (MÁS O MENOS) ABAJO */ document.title=(desplazado + (altoViewPort * 1.1))+" > "+document.documentElement.scrollHeight; if((desplazado + (altoViewPort * 1.1)) > document.documentElement.scrollHeight) { document.querySelector("footer").className = "piealto"; } else document.querySelector("footer").className = ""; /* (EL CONDICIONAL PREVIO ES UN EXPERIMENTO A OJÍMETRO COMBINADO CON EL CSS) */ } onload = carga; onscroll = mueve; </script>

El mecanismo es harto sencillo.

«Al cargar el documento la primera imagen está completamente visible, pero a las demás el escript les da un formato de "inclinadas hacia atrás" con la clase cierra. Hay 'comentadas' algunas líneas para agregar efectos a esa imagen inicial y probar otras opciones, que serán puramente estéticas.
Al arrastrar el documento se dispara otra función que mide "en tiempo real" (ya no hay un retardo como en el ejemplo del header) la distancia desplazada del documento y cada una de las posiciones de las imágenes. Si el lado superior de alguna (medido con offsetTop) se encuentra por encima de la mitad del viewport (medido con window.innerHeight) le quita la clase cierra y pone la clase abre, con su animación que "resetea" el formato de inclinación 3D.
El efecto solamente funciona "de arriba hacia abajo" y si las imágenes son desplazadas otra vez hasta quedar por debajo del viewport, la clase no se cambia. Al volver a desplazarlas hacia arriba, el escript intenta eliminar la clase cierra (que no está) y repone la clase abre (que ya estaba), por lo que no se ve ningún cambio.
»

El offsetTop devuelve la distancia en pixeles de un elemento hasta el borde superior del documento. Para el caso, con imágenes encolumnadas verticalmente, cada una tendrá un valor distinto, mayor a medida que aumente la cantidad de fotos.

Lo que hacen self.pageYOffset o document.documentElement.scrollTop es medir (también en px) la parte del documento que queda oculta "por encima" del viewport; al estar en el inicio, será de 0px, y al desplazar comenzará a aumentar su valor.

Cuando la distancia al borde superior de una imagen (su offsetTop) sea menor que la distancia desplazada (variable desplazado en el ejemplo) se puede ejecutar una instrucción para (p.e.) cambiar una clase. Pero en la práctica no nos sirve, porque para cuando la foto llegó arriba ya es un poco tarde para mostrar efectos, así que a la distancia desplazada le sumamos la mitad de la altura del viewport (window.innerHeight / 2) así el cambio no se produce al llegar arriba sino más abajo, a la mitad de la pantalla.

esquema funcionamiento offsetTop y scrollTop
esquema funcionamiento offsetTop y scrollTop

En esta imagen se pueden apreciar como acotaciones las distancias que mide el escript. El borde superior es el comienzo del documento, las líneas amarillas son cada offsetTop en las fotos, y son valores fijos al tope de la página que se revisan a cada paso de la scrollbar; la marca verde es la mitad del alto del viewport, que también es un valor fijo; la sinuosa roja es el scrollTop, que arranca de cero y va creciendo a medida que haya más documento por arriba del viewport, así que en algún momento del arrastre su distancia sumada al medio viewport va a igualar y superar a la de los distintos offsetTop de las imágenes, y esa comparación condicional es lo que cambia las clases en cada img cuando pasan la mitad del panel.

En en el pen anterior hay además un experimento para animar un pie de página, que iba a eliminar del código, pero seguro a alguien le va a interesar estudiarlo un poco para hacer una versión mejorada. Por eso quedó.

lazy load: carga de imágenes al entrar al viewport

El efecto anterior no tiene demasiado provecho práctico, por lo que a Kseso se le ocurrió una variante que no será muy CSS, pero iba a ser más útil. Y era crear un "lazy load", para que las imágenes se cargaran en el documento a medida que se avanzaba en la navegación. De esta forma el visitante se ahorraría algunos bytes y peticiones al server si solamente miraba el comienzo de la página y después se iba sin recorrerla toda.

Ver Demo

Esta demo sólo carga imágenes una a una desde el comienzo hasta el final (no al revés ni saltando a un anclaje en el documento) y no tiene fallback ante un javascript desactivado.

Esto último solamente se puede hacer usando etiquetas noscript, pero al intentar la tercera versión ya aparecieron problemas.

Como habrán visto, lo que se carga originalmente son imágenes dummy y sus rutas finales están metidas en un data-source. Al arrastrar el documento y quedar visibles, un escript reemplaza el src original por el valor del data-source, y entonces la nueva imagen aparece. Esto tiene el inconveniente de obligarnos a escribir el src y el data-....

Yo probé una segunda versión mejorada y más eficiente, que tenía etiquetas img con las medidas finales (práctica común y recomendable para mantener diseños ante errores de carga) pero sin src, solamente había un data-source=; y al hacerse visible mediante el scrolling se reemplazaba ese atributo por src=, con lo que la imagen tenía un formato válido y se cargaba. Hago énfasis en lo de "formato válido", porque mi idea no validaba ni disfrazada de chancho. Aunque les aseguro que funciona.

Y no, no se asusten, no la voy a publicar.

Lazy load: a la tercera la vencida

Así que salté a la ya mencionada tercera versión, que además tiene incluidos los noscript con una diferencia : en vez de agregarlos debajo de las imágenes de relleno a modificar con JS -como hacen algunas librerías-, los puse alrededor de esas imágenes, pero con la url verdadera.

La idea es que si no está habilitado javascript, deberían aparecer las imágenes dentro; si está habilitado, a medida que desplazamos el documento el escript elimina las etiquetas de noscript y deja accesible la imagen, que entonces sí se va a cargar.

Bueno, tampoco funcionó. Chrome convierte las etiquetas dentro de noscript a texto, aunque si recargaba el documento, al fin aparecían como imágenes. Claro que si queremos ahorrar recursos y peticiones al servidor, no vamos a pretender que el usuario recargue un documento. Aunque de última, esto lo podemos dejar pasar pensando que en algún momento va a ser corregido.

De todas formas, ni en Firefox ni en Chrome (donde lo probé) hay manera de leer el offsetTop de un noscript. Es un elemento con display:none y no encontramos manera de cambiarlo, por lo que es imposible medir su distancia al borde superior (siempre devuelve '0').

Así que para cumplir con ustedes usé un método semanticamente discutible y muy poco flexible. Metí cada noscript y su img dentro de un figure (por elegir un contenedor) y le di a estos la altura declarada (o puede ser la natural, según el caso) de la imagen que está en su nieto.

captura inspector de código con imágenes sin cargar
Captura del inspector de código: las imágenes no han sido cargadas

Ahora sí puedo hallar el offsetTop que me negaba noscript, y al llegar cada figure al borde inferior del viewport el escript captura el contenido HTML del noscript y hace el reemplazo que en la práctica lo "elimina" y deja en su lugar nada más que el código de la imagen. Entonces el navegador con javascript activado la procesa y muestra la foto. Sin javascript, nada de esto ocurre y se ven los contenidos de noscript desde el comienzo.

See the Pen Lazy-load de imágenes con el desplazamiento. (2) by Kseso (@Kseso) on CodePen.

Hay dos novedades en el ejemplo en lo referente al javascript:

  1. Una es el uso de parentElement. Como su nombre lo indica, apunta al elemento padre del que en ese momento estamos trabajando. En nuestro caso, estamos contando y leyendo los noscript, y en algún momento necesitamos saber la distancia al tope del documento de su contenedor padre (el elemento figure), así que en vez de escribir toda la línea que lo identifique, aprovechamos que ya está ubicado su hijo y lo usamos de referencia con un método más cómodo.
  2. Otra es outerHTML, que conociendo a innerHTML resulta fácil deducir su función : en vez de capturar o escribir el contenido HTML de un elemento, captura o reescribe el código completo de ese elemento, incluyendo sus propias etiquetas de apertura y cierre.

Como se darán cuenta, el mecanismo es bastante engorroso para meterlo en un documento "de producción", y ni de cerca pensemos que se pueda usar en cualquier caso como si fuese una library. Es para estudiar y adaptar según cada necesidad.

En otras palabras : cutanpasters, abstenerse ;-)

Nota del Editor: En esta última demo no te dejes distraer por el efecto parallax y el filtro: blur() añadido por el editor del blog sin conocimiento de Furoya. Es un mero artificio visual. De lo que trata es, como bien señala el autor, de cargar las imágenes (u otro recurso) cuando es necesario y no inicialmente con el documento.

Para los jugones de Js y CSS

Esta última construcción Parallax y lazy load images la tienes disponible para que experimentes con ella, además de en este post, en:

  1. Pen en Codepen
  2. En Github

Autoría

Furoya: Autor del artículo

Artículo original de Furoya.
La intención del autor con sus colaboraciones no es que los artículos sirvan para hacer un copy&paste de códigos sino que comprendas y aprendas la lógica y el cómo trabaja javaScript.
Y a partir de lo expuesto experimentes tú.
El autor del post y el editor del blog te animamos a que plantees tus dudas o reflexiones y que compartas tus realizaciones en base a lo expuesto en los comentarios. Recuerda que puedes incluir pens (ejemplos en Codepen.io) en ellos.