Javascript con Furoya: Parallax y Lazy-Load ¿estoy dentro o fuera del viewport? 16.7.17
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?
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.
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.
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.
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.
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:
- 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 losnoscript
, y en algún momento necesitamos saber la distancia al tope del documento de su contenedor padre (el elementofigure
), 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. - Otra es
outerHTML
, que conociendo ainnerHTML
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:
Autoría
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.
Una vez más: ¡Que buena "pareja"!
ResponderEliminarFelicidades. :-D
:clap: :clap:
Gracias por lo que me toca, HenryGR. En este artículo hay mucho trabajo de Kseso, que se hizo una demo y un gráfico explicando los alcances de 'scrollTop' y 'offsetTop', y les quedaron muy bien.
ResponderEliminarEso, eso. El mérito mío por un "...y ya que te has puesto, Furoya... y un dibujito.
EliminarPor mi encantado, Henry 😁
Ahora en serio y recordando (que este post es de Junio de 2015) la cuestión es que en el momento de pasarme Furoya un "borrador" de este post había estado buscando qué había sobre "lazy load" y todo me parecía demasiado para lo que se pretendía.
Y eso, Furoya volvió a hacer su magia y reducir todo a 13 líneas de puro javascript.
Me alegro que el "repost" esté gustando tanto o más que en el momento de su publicación.
Un saludo
Hola, necesito ayuda con el script de un menú dropdown de tres puntos. El asunto es que al darle a los enlaces este se cierra sin dejar acceder a los links. Este es el menu: https://codepen.io/ryanmorr/pen/vLKvqe
ResponderEliminarHola Ernestina Rojas.
ResponderEliminarNo me lo tomes a mal, pero... ¿notaste que el artículo no tiene nada que ver con menúes anidados? ¿Y que Ryan Morr tampoco anda por aquí?
No te digo que le preguntes a él, porque vi que ya lo hiciste, y aún no te respondió, pero a la fecha no pasó ni una semana; en la comunidad de Codepen suelen demorar más (cuando responden, si se enteran).
Aproveché para darle una mirada, pero desgraciadamente en la máquina que estoy el efecto ni se ve, y hay tantos ejemplos de menú anidado en toda la web que no me imagino qué tiene ése de especial para que quieras hacerlo andar como te debería.
Por este blog hay varios ejemplos en puro CSS que funcionan ¿ninguno te sirve? (Aunque no recuerdo si hay anidados).
De cualquier forma, tu consulta no se entiende, porque "si ya le diste al enlace" entonces "ya accediste al link" ¿no?.
Adiós, y mucha suerte con tus menúes.