Scroll suave del documento con CSS (y 12 líneas de javascript)

Nueva entrega y colaboración de la serie Javascript con @Furoya. En esta ocasión dedicada a controlar a voluntad la velocidad de desplazamiento del documento al hacer scroll.

Scroll suave del documento con CSS (y 12 líneas de javascript)

✎ 2
COLABORACIÓN AUTOR INVITADO
Scrolling suave del documento en CSS (con una ayudita de javascript)

Vamos a comenzar aclarando un punto : CSS aún no tiene cómo leer el desplazamiento de una página, y no existen propiedades ni valores que lo modifiquen.

Hay muchos (demasiados) pedidos en foros, comunidades y comentarios de blogs reclamando efectos CSS para el movimiento generado por una scrollbar, que salvo aisladas excepciones terminan en una sugerencia para caer en javascript, o su variante JQuery.

Uno de los más comunes es el de desplazamiento suave. Algo que los navegadores ya tienen (yo lo tengo configurado al menos en Chrome y Firefox) pero que no disponen de muchos parámetros de ajuste. Oficialmente, dijimos, es imposible de lograr con estilos; pero hay algunos casos ya publicados que hacen una transición apoyada por javascript en la captura y asignación de valores, no en la animación que sí es trabajo del CSS.

Es un truco, no un método. No se podrá aplicar a todos los documentos en cualquier caso, pero eso no nos va a quitar el sueño al momento de intentar unos ejemplos experimentales aquí en el blog. Porque la idea sigue siendo entender cómo funciona, más que usarlo en una página real.

Cómo emular desplazamientos con un moiré (consecuente)

Empecemos por uno de estos casos de laboratorio, que en realidad sí puede existir en páginas de producción.

Es una galería de imágenes encolumnadas que se desplazan verticalmente con la scrollbar. La dificultad principal que encontramos al encarar el proyecto es que debemos desenganchar el movimiento de la barra del arrastre del documento, para darle después un desplazamiento suave y amortiguado. configurado por nosotros, pero que termine en la misma posición que le daría la scrollbar.

El truco es muy simple.

See the Pen Smooth scroll [1]. (Experimental). by solipsistaCP (@solipsistacp) on CodePen.

Ver Demo a Full

Y hablando de simpleza, el escript ya es minimalista.

<script type="text/javascript"> onload = inicia; //EJECUTA AL CARGAR EL DOCUMENTO onscroll = desplaza; //EJECUTA AL TERMINAR EL DESPLAZAMIENTO var desplazamiento, contenedor; function inicia() { /* REFIERE AL ELEMENTO CONTENEDOR DEL DOCUMENTO */ contenedor = document.querySelector("main"); /* MIDE LA ALTURA DEL CONTENEDOR Y LA PASA AL body */ document.body.style.height = contenedor.offsetHeight + "px" } function desplaza() { /* PASA LA MISMA DISTANCIA DE DESPLAZAMIENTO AL CONTENEDOR COMO top NEGATIVO */ contenedor.style.top = -pageYOffset + "px"; } </script>

Todo lo demás lo hace el CSS, como corresponde a este blog. Les explico un poco el JS:

  • No usé addEventListener() para no complicar el código, pero nada impide que en sus pruebas aprovechen para practicar un poco con él. Aquí hay dos eventos que disparan cada uno su función, para cuando se carga el documento y para cuando se lo desplaza.
  • Hay dos variables globales que llenamos con las funciones siguientes:
    1. La primera ubica en una variable la referencia al elemento que contiene al documento (las imágenes, los títulos, los enlaces, ...); y luego lo mide en altura para pasarle ese valor al body. Esto es porque el main tiene position:fixed, por lo que no ocupa espacio en el cuerpo del documento y éste no sabe realmente cuánto mide. Con esto logramos que body (desplazable verticalmente) y main (que está fijo y no se desplaza) de alguna forma puedan emparejarse si los movemos con cualquier método sobre el eje "y" una misma distancia.
    2. Para el caso del body, claramente la manera de desplazarlo es con la barra, el teclado, el puntero de dirección sobre la pantalla, el arrastre con el dedo, la ruedita, en fin, los modos usuales. Y por eso la siguiente función usa pageYOffset que captura la cantidad de pixeles desplazados en vertical para luego pasar ese valor (convertido a negativo, claro) como top del contenedor de nuestro documento. Si el cuerpo se mueve (p.e.) 500px hacia arriba, la posición de la galería sube esos 500px por obra del JS que reescribe el valor top en su CSS.
    3. La magia la pone una regla de transición. Al cambiar el valor de top la hoja de estilos ya aplica su transition: top 1500ms cubic-bezier(.5,.33,.3,.9) 200ms que hace un desplazamiento suave y amortiguado simulando que es el de la scrollbar.

No sé si se fijaron en un detalle de la demo anterior. Resulta que el fondo tenía un dibujo de gradientes, que estaba puesto en el contenedor de la galería. Si el fondo hubiese estado en el body tendríamos un efecto curioso : porque los colores se desplazarían siguiendo la scrollbar, y después se movería el contenido sobre él. Esto puede ser útil en algunos casos, sin importar ya qué tipo de transición usemos.

Antes de pasar a la siguiente versión de desplazamientos, les muestro un ejemplo de fondos desfasados, por si les sugiere alguna idea.

See the Pen Smooth scroll [2]. (Experimental). by solipsistaCP (@solipsistacp) on CodePen.

Ver Demo a Full

Cómo emular rotaciones (con un paralaje antecedente)

Si por casualidad no se les había ocurrido combinar animaciones de fondo, seguro que al ver todas las demos presentadas y linkeadas se les apareció la imagen del parallax.

Y sí, es posible capturar el valor de desplazamiento para aplicarlo a elementos desenganchados de su arrastre, entonces podemos elegir secciones de la página y darles distintas velocidades de animación. En verdad podemos darles distintas animaciones que no necesariamente sean de desplazamiento vertical.

Éste sería un ejemplo más bien simple, muy mejorable, pero que les da una perspectiva cabal de las enormes posibilidades que tiene el escript (para ser experimental, digo).

See the Pen Smooth scroll [3]. (Experimental). by solipsistaCP (@solipsistacp) on CodePen.

Ver Demo a Full

Quería agregar un "motorcito con polea" detrás del engranaje, pero hubiese sido ensuciar el código con elementos innecesarios. Esas cosas se las dejo a diseñadores de verdad, que seguro tienen un poco más de creatividad que yo.

Por ahora, demos una vista rápida al javascript.

<script type="text/javascript"> onload = inicia; //EJECUTA AL CARGAR EL DOCUMENTO onscroll = desplaza; //EJECUTA AL TERMINAR EL DESPLAZAMIENTO var desplazamiento, contenedor; function inicia() { /* REFIERE AL ELEMENTO CONTENEDOR DEL DOCUMENTO */ contenedor = document.querySelector("main"); /* MIDE LA ALTURA DEL CONTENEDOR Y LA PASA AL body */ document.body.style.height = contenedor.offsetHeight + "px"; } function desplaza() { /* MIDE EL DESPLAZAMIENTO VERTICAL DEL DOCUMENTO */ desplazamiento = pageYOffset; /* PASA ESA MISMA DISTANCIA AL CONTENEDOR COMO top NEGATIVO */ contenedor.style.top = -desplazamiento + "px"; /* CALCULA LA ROTACIÓN DEL ENGRANE SEGÚN EL DESPLAZAMIENTO Y LA APLICA */ document.getElementById("engrane").style.transform = "rotate("+ (-desplazamiento*360/754) +"deg)"; } </script>

Es practicamente igual a los otros, lo único agregado es el giro del engrane, que parece tener un número mágico. En realidad 754 es el diámetro en pixeles de la imagen circular, el cálculo (simple matemática) rota ese borde dentado tantas veces como la distancia que se desplaza la galería. Usando su conversión a grados, claro, porque el giro es un transform:rotate(Gdeg).

Para que quede más evidente : si desplazamos 754px el documento, la rueda debe dar un giro completo de -360° porque su diámetro es de 754px. Si desplazamos 1508px, girará -720° (el equivalente a 2 vueltas). La dirección del giro se calcula sola, según la posición de origen.

Un detalle a mencionar es que para el ejemplo las medidas están puestas en pixeles, lo que puede dificultar su adaptabilidad a diferentes pantallas cuando el ancho sea menor a 800px. Es algo que se puede corregir, si alguna vez lo tienen que usar fuera de una demo.

Cómo emular anclajes (con un replicado lamentable)

Cómo emular anclajes (con un replicado lamentable)

Vamos a poner un último caso, ya sin muchas explicaciones, porque la mecánica será más o menos la misma. Y si bien los anteriores podrían pasar bajo determinadas circunstancias como algo factible para una página verdadera, éste ya es muy experimental. Es para refregarle en la cara a los que dicen que no se puede, aunque nos terminen respondiendo con un entonces, no se debe.

Hasta ahora solamente capturamos valores de tamaño para pasar de una capa a otra. Es una práctica común en cualquier diseño. Pero no se debe abusar del recurso a menos que lo usemos para estos blogs donde siempre se muestran cosas raras.

Supongamos que ya no queremos desplazar el documento con los clásicos escrols, sino que vamos a usar anclas. Un típico menú de navegación para saltar a distintos artículos de la página.

El problema empieza a ser evidente :

  1. los enlaces van a saltar hasta el elemento que tenga el identificador que esté en su hiper-referencia, pero ese salto debe hacerlo en una capa invisible.
  2. Una vez desplazado el documento, entonces se mueve la capa visible hasta la misma posición.
  3. Para que HTML mueva la capa invisible, debe contener todos los elementos con sus identificadores en la misma posición que en la capa visible.

En principio no alcanza con copiar las medidas, hay que replicar todo el documento y que el navegador mueva el original que no se muestra para que después JS+CSS hagan su animación con la copia que se ve.

See the Pen Smooth scroll [4]. (Experimental). by solipsistaCP (@solipsistacp) on CodePen.

Ver Demo a Full

Es verdad, podemos suponer que la copia va a salir de la caché de la máquina y que en realidad no estamos bajando el doble de contenido, pero aún así la idea es impresentable.

Pensemos solamente en que todas las id's se duplican en el mismo documento, y ya sabemos que no puede existir más de una con el mismo nombre. La distribución de las capas también es crítica, porque el navegador va a interpretar con JS sólo a las primeras así que en el DOM la versión original debe estar antes que la copia. Y ningún otro escript debe hacer referencia a los identificadores, porque también van a buscar al primero de cada uno, que es el que no se ve, y no va a leer o modificar nada para el usuario.

Así y todo, si queremos tomarnos más trabajo, siempre podemos ubicar las coordenadas y tamaños de los elementos que tengan anclajes, y replicar solamente eso en la capa que desplace el menú usando posiciones absolutas, reescribiendo sus valores al vuelo allí y en los botones del menú, para que coincidan y se diferencien de los que estaban en el código fuente, y que ya no van a servir para desplazarse pero aún serían vistos por el CSS y el JS ajenos a nuestro efecto que tenga la página.

Cómo seguir esto (con una sugerencia ajena)

En resumen, lo que quiero decir es que dependiendo de nuestras necesidades y las ganas que le pongamos al producto, hay montones de efectos para aprovechar el arrastre de la escrolbar; pero todos son más o menos iguales en el código. Con entender su mecánica es más que suficiente para empezar a inventar. (Y Edgar Gutiérrez ya estuvo proponiendo uno y dos)

Artículos del autor relacionados

Otros artículos de la serie "Javascript con Furoya" que tienen al scroll y/o sus scrollbars como protagonistas:

  1. Averiguar tamaños y pasar su valor a Css. Caso de uso: coloreado del scroll
  2. Parallax y Lazy-Load ¿estoy dentro o fuera del viewport?
  3. control del scroll. Eventos y efectos al desplazar la página

Créditos y Autoría

Créditos de imagen: La imagen del inicio y el engrane de Internet Archive Book Images; las galerías contienen fotografías de obras que pertenecen a sus respectivos autores.]

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.

Comentarios: 2

  1. Primero quiero darles las gracias por estas joyas, que a pesar de la gran cantidad de minas pocas encuentro tan únicas y pulidas.

    Segundo, no soy muy bueno en javascript (por no decir que ni una pizca), por lo que no sabría como, pero sería interesante ver como se le puede sacar partido a usar unidades relativas como las "viewportheight" o "viewportwidth" (vh o vw) en vez de pixeles, para calcular los desplazamientos.

    Saludos.

    ResponderEliminar
    Respuestas
    1. De nada, a mí me gusta escribir estos artículos. Y en este blog, porque comparto tu opinión sobre el nivel que ofrece. De ahí mis reparos al comenzar a colaborar, porque debía estar a la altura del resto de los artículos y de los colaboradores. Y es difícil.

      El tema de las unidades relativas es puro cálculo. Javascript trabaja con los valores que maneja el navegador, que son nada más que pixeles. Es la única medida que entiende.
      CSS debe hacer la conversión antes de calcular, así que si un hijo tiene el 50% de ancho de su padre, el escript detrás de la hoja de estilos le hace un
      [code](50/100) * miElemento.parentElement.offsetWidth[/code]
      y ya sabe cuántos pixeles son ese 50% para aplicarlos.

      Claro que hay un problema : el redondeo. Y otro más grave : el binario. Lo voy a explicar un poco en la siguiente entrega, pero te imaginarás que el subpixel es nada más para cálculos intermedios; al aplicarlo, la medida más chica es justamente el pixel, y si el ancho a medir tiene 173px no es posible darle a su hijo el 50% como 86.5px . Cada motor de rendering aplica su fórmula de redondeo y allí aparecen los espacios fantasmas, las rayas de colores en un borde, las posiciones desfasadas, ...

      Lo que hace javascript es ver la medida tal como lo hace el navegador, pero te permite hacer tu propio redondeo y asegurarte de que se vea igual en cualquier browser. Así que en realidad se puede tomar cualquier medida apuntando a un elemento, y hacer tus propios 'vh', 'vw', '%'; de la misma forma que en el ejemplo del engranaje se podría haber hecho la cuenta del perímetro en el escript, en vez de calcularla "afuera" (diámetro por pi) y poner el valor a mano.
      [code]
      var perimetro = document.getElementById("engrane").offsetWidth * 3.1416;
      document.getElementById("engrane").style.transform = "rotate("+ (-desplazamiento*360/perimetro) +"deg)";
      [/code]
      Eso lo hubiese ajustado en el caso de existir otro programa que le cambiara su tamaño a la imagen según —por ejemplo— la resolución de pantalla.

      Eliminar

EsCss RSS del Blog RSSS Comentarios Humans.txt ᛯ Diseno por Kseso SiteMap