Te guste o no, el efecto parallax llegó para quedarse. Cuando se usa con prudencia, puede agregar profundidad y sutileza a una app web. Sin embargo, el problema es que implementar el paralaje de manera eficiente puede ser un desafío. En este artículo, analizaremos una solución que tiene un buen rendimiento y, lo que es igual de importante, funciona en varios navegadores.
Resumen
- No uses eventos de desplazamiento ni
background-position
para crear animaciones de paralaje. - Usa transformaciones 3D de CSS para crear un efecto de paralaje más preciso.
- En Mobile Safari, usa
position: sticky
para asegurarte de que se propague el efecto de paralaje.
Si quieres la solución integrada, ve al repositorio de GitHub de Samples de elementos de IU y obtén el ayudante de JS de Parallax. Puedes ver una demostración en vivo del control deslizante de paralaje en el repositorio de GitHub.
Problemas de paralaje
Para empezar, analicemos dos formas comunes de lograr un efecto de paralaje y, en particular, por qué no son adecuadas para nuestros fines.
No se recomienda: usar eventos de desplazamiento
El requisito clave del paralaje es que debe estar acoplado al desplazamiento. Para cada cambio en la posición de desplazamiento de la página, se debe actualizar la posición del elemento de paralaje. Aunque parezca sencillo, un mecanismo importante de los navegadores modernos es su capacidad de trabajar de forma asíncrona. Esto se aplica, en nuestro caso particular, a los eventos de desplazamiento. En la mayoría de los navegadores, los eventos de desplazamiento se entregan como “mejor esfuerzo” y no se garantiza que se entreguen en cada fotograma de la animación de desplazamiento.
Esta información importante nos indica por qué debemos evitar una solución basada en JavaScript que mueva elementos en función de eventos de desplazamiento: JavaScript no garantiza que el efecto de paralaje se mantenga a la par de la posición de desplazamiento de la página. En versiones anteriores de Safari para dispositivos móviles, los eventos de desplazamiento se entregaban al final del desplazamiento, lo que imposibilitaba crear un efecto de desplazamiento basado en JavaScript. Las versiones más recientes sí entregan eventos de desplazamiento durante la animación, pero, al igual que Chrome, en función del "mejor esfuerzo". Si el subproceso principal está ocupado con cualquier otro trabajo, los eventos de desplazamiento no se entregarán de inmediato, lo que significa que se perderá el efecto de paralaje.
Error: Actualización de background-position
Otra situación que nos gustaría evitar es pintar en cada marco. Muchas soluciones intentan cambiar background-position
para proporcionar el aspecto de paralaje, lo que hace que el navegador vuelva a pintar las partes afectadas de la página durante el desplazamiento, lo que puede ser lo suficientemente costoso como para bloquear la animación de manera significativa.
Si queremos cumplir con la promesa del movimiento con paralaje, queremos algo que se pueda aplicar como una propiedad acelerada (que hoy significa apegarse a las transformaciones y la opacidad) y que no dependa de eventos de desplazamiento.
CSS en 3D
Tanto Scott Kellum como Keith Clark hicieron un trabajo significativo en el área del uso de CSS 3D para lograr el movimiento de paralaje, y la técnica que usan es, en efecto, la siguiente:
- Configura un elemento contenedor para que se desplace con
overflow-y: scroll
(y probablementeoverflow-x: hidden
). - A ese mismo elemento, aplica un valor
perspective
y unperspective-origin
establecido entop left
o0 0
. - A los elementos secundarios de ese elemento, aplícales una traslación en Z y vuelve a escalarlos para proporcionar un movimiento de paralaje sin afectar su tamaño en la pantalla.
El CSS para este enfoque se ve de la siguiente manera:
.container {
width: 100%;
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
perspective: 1px;
perspective-origin: 0 0;
}
.parallax-child {
transform-origin: 0 0;
transform: translateZ(-2px) scale(3);
}
Que supone un fragmento de HTML como este:
<div class="container">
<div class="parallax-child"></div>
</div>
Cómo ajustar la escala para la perspectiva
Si alejas el elemento secundario, este se hará más pequeño de manera proporcional al valor de perspectiva. Puedes calcular cuánto se deberá escalar con esta ecuación: (perspectiva - distancia) / perspectiva. Dado que lo más probable es que deseemos que el elemento de paralaje tenga paralaje, pero que aparezca en el tamaño en el que lo creamos, debería aumentarse de esta manera, en lugar de dejarlo como está.
En el caso del código anterior, la perspectiva es 1px y la distancia en Z de parallax-child
es -2px. Esto significa que el elemento deberá escalarse 3 veces, que puedes ver que es el valor conectado al código: scale(3)
.
Puedes sustituir el contenido al que no se aplicó un valor translateZ
. Esto significa que la escala es (perspective - 0) /
perspective, que da un valor de 1, lo que significa que se escaló
ni hacia arriba ni hacia abajo. Es muy útil.
Cómo funciona este enfoque
Es importante tener claro por qué funciona, ya que usaremos ese conocimiento en breve. El desplazamiento es, en realidad, una transformación, por lo que se puede acelerar. En su mayoría, implica mover capas con la GPU. En un desplazamiento típico, que es uno sin ninguna noción de perspectiva, el desplazamiento se produce de forma 1:1 cuando se compara el elemento de desplazamiento y sus elementos secundarios.
Si desplazas un elemento hacia abajo en 300px
, sus elementos secundarios se transforman hacia arriba en la misma cantidad: 300px
.
Sin embargo, aplicar un valor de perspectiva al elemento de desplazamiento altera este proceso, ya que cambia las matrices que sustentan la transformación de desplazamiento.
Ahora, un desplazamiento de 300 px solo puede mover los elementos secundarios en 150 px, según los valores de perspective
y translateZ
que elijas. Si un elemento tiene un valor de translateZ
de 0, se desplazará a 1:1 (como solía hacerlo), pero un elemento secundario que se empuje en Z lejos del origen de la perspectiva se desplazará a una velocidad diferente. Resultado neto: movimiento de paralaje. Y, lo que es muy importante, esto se controla automáticamente como parte del mecanismo de desplazamiento interno del navegador, lo que significa que no es necesario escuchar eventos scroll
ni cambiar background-position
.
Un inconveniente: Safari para dispositivos móviles
Cada efecto tiene advertencias, y una importante para las transformaciones es la conservación de los efectos 3D en los elementos secundarios. Si hay elementos en la jerarquía entre el elemento con una perspectiva y sus elementos secundarios de paralaje, la perspectiva 3D se "aplana", lo que significa que se pierde el efecto.
<div class="container">
<div class="parallax-container">
<div class="parallax-child"></div>
</div>
</div>
En el código HTML anterior, .parallax-container
es nuevo y aplanará de manera efectiva el valor perspective
, por lo que perderemos el efecto de paralaje. En la mayoría de los casos, la solución es bastante sencilla: debes agregar transform-style: preserve-3d
al elemento, lo que hace que se propague cualquier efecto 3D (como nuestro valor de perspectiva) que se haya aplicado más arriba en el árbol.
.parallax-container {
transform-style: preserve-3d;
}
Sin embargo, en el caso de Safari para dispositivos móviles, las cosas son un poco más complicadas.
Aplicar overflow-y: scroll
al elemento del contenedor funciona técnicamente, pero a costa de poder lanzar el elemento de desplazamiento. La solución es agregar -webkit-overflow-scrolling: touch
, pero también aplanará el perspective
y no obtendremos ningún paralaje.
Desde el punto de vista de la mejora progresiva, esto probablemente no sea un problema demasiado grande. Si no podemos usar el efecto de paralaje en todas las situaciones, nuestra app seguirá funcionando, pero sería bueno encontrar una solución alternativa.
position: sticky
al rescate
De hecho, hay cierta ayuda en forma de position: sticky
, que existe para permitir que los elementos se “fijen” en la parte superior del viewport o en un elemento superior determinado durante el desplazamiento. La especificación, como la mayoría de ellas, es bastante extensa, pero contiene una pequeña joya útil:
A primera vista, esto puede no parecer muy importante, pero un punto clave de esa oración es cuando se refiere a cómo se calcula exactamente la fijación de un elemento: "el desplazamiento se calcula en función del ancestro más cercano con un cuadro de desplazamiento". En otras palabras, la distancia para mover el elemento fijo (para que aparezca adjunto a otro elemento o al viewport) se calcula antes de que se aplique cualquier otra transformación, no después. Esto significa que, al igual que en el ejemplo de desplazamiento anterior, si el desplazamiento se calculó en 300 px, existe una nueva oportunidad para usar perspectivas (o cualquier otra transformación) para manipular ese valor de desplazamiento de 300 px antes de que se aplique a cualquier elemento fijo.
Si aplicamos position: -webkit-sticky
al elemento de paralaje, podemos “revertir” de manera eficaz el efecto de aplanamiento de -webkit-overflow-scrolling:
touch
. Esto garantiza que el elemento de paralaje haga referencia al principal más cercano con un cuadro de desplazamiento, que en este caso es .container
. Luego, de manera similar a antes, .parallax-container
aplica un valor perspective
, que cambia el desplazamiento calculado y crea un efecto de paralaje.
<div class="container">
<div class="parallax-container">
<div class="parallax-child"></div>
</div>
</div>
.container {
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
}
.parallax-container {
perspective: 1px;
}
.parallax-child {
position: -webkit-sticky;
top: 0px;
transform: translate(-2px) scale(3);
}
Esto restablece el efecto de paralaje para Mobile Safari, lo que es una excelente noticia.
Advertencias sobre el posicionamiento fijo
Sin embargo, existe una diferencia: position: sticky
modifica la mecánica del paralaje. El posicionamiento fijo intenta fijar el elemento al contenedor de desplazamiento, mientras que una versión no fija no lo hace. Esto significa que el paralaje con pegajoso termina siendo el inverso del que no tiene lo siguiente:
- Con
position: sticky
, cuanto más cerca esté el elemento de z=0, menos se moverá. - Sin
position: sticky
, cuanto más cerca se encuentre el elemento de z=0, más se moverá.
Si todo esto te parece un poco abstracto, mira esta demostración de Robert Flack, en la que se muestra cómo los elementos se comportan de manera diferente con y sin posicionamiento fijo. Para ver la diferencia, necesitas Chrome Canary (que es la versión 56 en el momento de escribir este artículo) o Safari.
Demostración de Robert Flack que muestra cómo position: sticky
afecta el desplazamiento de paralaje.
Diversos errores y soluciones
Sin embargo, como con cualquier cosa, todavía hay irregularidades que se deben suavizar:
- La compatibilidad con la función de fijar elementos no es coherente. La compatibilidad aún se está implementando en Chrome, Edge no la admite en absoluto y Firefox tiene errores de pintura cuando se combina la fijación con transformaciones de perspectiva. En esos casos, vale la pena agregar un poco de código para agregar solo
position: sticky
(la versión con el prefijo-webkit-
) cuando sea necesario, que es solo para Mobile Safari. - El efecto no “funciona” en Edge. Edge intenta controlar el desplazamiento a nivel del SO, lo que, en general, es algo bueno, pero, en este caso, evita que detecte los cambios de perspectiva durante el desplazamiento. Para solucionar este problema, puedes agregar un elemento de posición fija, ya que parece cambiar Edge a un método de desplazamiento que no es del SO y garantiza que se tengan en cuenta los cambios de perspectiva.
- “El contenido de la página se hizo enorme”. Muchos navegadores tienen en cuenta la escala a la hora de decidir el tamaño del contenido de la página, pero Chrome y Safari no tienen en cuenta la perspectiva. Por lo tanto, si se aplica, por ejemplo, una escala de 3x a un elemento, es posible que veas barras de desplazamiento y otras similares, incluso si el elemento está en 1x después de que se aplicó
perspective
. Para solucionar este problema, puedes escalar los elementos desde la esquina inferior derecha (contransform-origin: bottom right
), lo que funciona porque hará que los elementos de gran tamaño crezcan en la "región negativa" (por lo general, la parte superior izquierda) del área desplazable. Las regiones desplazables nunca te permiten ver ni desplazarte hasta el contenido de la región negativa.
Conclusión
El efecto de paralaje es divertido cuando se usa con cuidado. Como puedes ver, es posible implementarlo de una manera que sea eficiente, acoplada al desplazamiento y multinavegador. Dado que requiere un poco de cálculo matemático y una pequeña cantidad de texto de plantilla para obtener el efecto deseado, creamos una pequeña biblioteca de ayuda y una muestra, que puedes encontrar en nuestro repositorio de GitHub de muestras de elementos de IU.
Pruébala y cuéntanos cómo te fue.