Te guste o no, el efecto de paralaje 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 efecto de paralaje de una manera que no afecte el rendimiento puede ser un desafío. En este artículo, analizaremos una solución que es eficiente y, lo que es igual de importante, funciona en todos los 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 el caso de Safari para dispositivos móviles, usa
position: sticky
para asegurarte de que se propague el efecto de paralaje.
Si quieres la solución lista para usar, ve al repositorio de GitHub de muestras de elementos de IU y obtén el JS de ayuda de Parallax. Puedes ver una demostración en vivo del desplazamiento con efecto de paralaje en el repositorio de GitHub.
Paralaje de problemas
Para comenzar, veamos dos formas comunes de lograr un efecto de paralaje y, en particular, por qué no son adecuadas para nuestros propósitos.
Incorrecto: Usar eventos de desplazamiento
El requisito clave del efecto de paralaje es que debe estar vinculado al desplazamiento. Por cada cambio en la posición de desplazamiento de la página, se debe actualizar la posición del elemento de paralaje. Si bien eso suena simple, un mecanismo importante de los navegadores modernos es su capacidad de trabajar de forma asíncrona. En nuestro caso particular, esto se aplica 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 importante información nos indica por qué debemos evitar una solución basada en JavaScript que mueva elementos según los eventos de desplazamiento: JavaScript no garantiza que el efecto de paralaje se mantendrá sincronizado con 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, de manera similar a Chrome, con el mayor esfuerzo posible. 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.
Incorrecto: Actualizando background-position
Otra situación que nos gustaría evitar es pintar en cada fotograma. 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 al desplazarse, y eso puede ser lo suficientemente costoso como para provocar un retraso significativo en la animación.
Si queremos cumplir con la promesa del movimiento de paralaje, necesitamos algo que se pueda aplicar como una propiedad acelerada (lo que hoy significa limitarse a las transformaciones y la opacidad) y que no dependa de los eventos de desplazamiento.
CSS en 3D
Tanto Scott Kellum como Keith Clark han realizado un trabajo significativo en el área del uso de CSS 3D para lograr el movimiento de paralaje, y la técnica que utilizan es, en efecto, la siguiente:
- Configura un elemento contenedor para que se desplace con
overflow-y: scroll
(y, probablemente,overflow-x: hidden
). - A ese mismo elemento, aplícale un valor de
perspective
y unperspective-origin
establecido entop left
o0 0
. - A los elementos secundarios de ese elemento, se les aplica una traslación en Z y se los vuelve a escalar para proporcionar 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);
}
Lo 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 empujas el elemento secundario hacia atrás, se reducirá proporcionalmente al valor de perspectiva. Puedes calcular cuánto se deberá escalar con esta ecuación: (perspectiva - distancia) / perspectiva. Dado que es muy probable que queramos que el elemento con efecto de paralaje se mueva con este efecto, pero que aparezca con el tamaño que le asignamos, debería aumentarse de esta manera, en lugar de dejarlo como está.
En el caso del código anterior, la perspectiva es 1 px y la distancia Z de parallax-child
es -2 px. Esto significa que el elemento deberá aumentarse en 3 veces, lo que puedes ver que es el valor que se conecta al código: scale(3)
.
Para cualquier contenido que no tenga un valor translateZ
aplicado, puedes sustituirlo por un valor de cero. Esto significa que la escala es (perspectiva - 0) / perspectiva, lo que da como resultado un valor de 1, lo que significa que no se aumentó ni disminuyó la escala. Es muy útil.
Cómo funciona este enfoque
Es importante que quede claro por qué funciona, ya que usaremos ese conocimiento en breve. El desplazamiento es, en efecto, una transformación, por lo que se puede acelerar. En su mayoría, implica desplazar 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 interfiere en 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á en una proporción de 1:1 (como antes), pero un elemento secundario que se aleje en Z del origen de la perspectiva se desplazará a una velocidad diferente. El resultado neto es un movimiento de paralaje. Y, lo que es muy importante, esto se controla automáticamente como parte del mecanismo interno de desplazamiento 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 sus limitaciones, 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 con efecto 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, de hecho, aplanará el valor de perspective
, por lo que perderemos el efecto de paralaje. En la mayoría de los casos, la solución es bastante sencilla: agrega transform-style: preserve-3d
al elemento para que 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 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 efecto de paralaje.
Desde el punto de vista de la mejora progresiva, esto probablemente no sea un problema grave. Si no podemos generar el efecto de paralaje en todas las situaciones, la app seguirá funcionando, pero sería bueno encontrar una solución alternativa.
¡position: sticky
al rescate!
De hecho, existe una ayuda en forma de position: sticky
, que permite que los elementos se "peguen" a la parte superior de la ventana gráfica o a un elemento principal determinado durante el desplazamiento. La especificación, como la mayoría, es bastante pesada, pero contiene una pequeña joya útil:
Quizás esto no parezca significar mucho a primera vista, pero un punto clave en esa oración es cuando se refiere a cómo se calcula exactamente la fijación de un elemento: "el desplazamiento se calcula con referencia al elemento superior más cercano con un cuadro de desplazamiento". En otras palabras, la distancia para mover el elemento fijo (para que parezca adjunto a otro elemento o a la ventana gráfica) se calcula antes de que se apliquen otras transformaciones, no después. Esto significa que, al igual que en el ejemplo de desplazamiento anterior, si el desplazamiento se calculó en 300 px, hay 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 "invertir" de manera eficaz el efecto de aplanamiento de -webkit-overflow-scrolling:
touch
. Esto garantiza que el elemento de paralaje haga referencia al elemento superior más cercano con un cuadro de desplazamiento, que en este caso es .container
. Luego, de manera similar a lo que ocurrió antes, el .parallax-container
aplica un valor de 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 Safari en dispositivos móviles, lo que es una excelente noticia.
Advertencias sobre el posicionamiento fijo
Sin embargo, hay una diferencia: position: sticky
sí altera la mecánica del paralaje. El posicionamiento fijo intenta, bueno, fijar el elemento al contenedor de desplazamiento, mientras que una versión no fija no lo hace. Esto significa que el efecto de paralaje con extremos fijos termina siendo el inverso del que no tiene extremos fijos:
- Con
position: sticky
, cuanto más cerca esté el elemento de z=0, menos se moverá. - Sin
position: sticky
, cuanto más cerca esté el elemento de z=0, más se moverá.
Si todo esto te parece un poco abstracto, consulta esta demostración de Robert Flack, en la que se muestra cómo se comportan los elementos de manera diferente con y sin el 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.

Una demostración de Robert Flack que muestra cómo position: sticky
afecta el desplazamiento con efecto de paralaje.
Errores y soluciones varios
Sin embargo, como con cualquier otra cosa, aún hay problemas que se deben solucionar:
- La compatibilidad con la función de fijar es inconsistente. La compatibilidad aún se está implementando en Chrome, Edge no tiene compatibilidad y Firefox tiene errores de pintura cuando sticky se combina 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, lo que solo se aplica a Mobile Safari. - El efecto no "simplemente funciona" en Edge. Edge intenta controlar el desplazamiento a nivel del SO, lo que generalmente es bueno, pero, en este caso, impide que detecte los cambios de perspectiva durante el desplazamiento. Para solucionar este problema, puedes agregar un elemento de posición fija, ya que parece que esto hace que Edge cambie 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 cuando deciden qué tan grande es el contenido de la página, pero, lamentablemente, Chrome y Safari no tienen en cuenta la perspectiva. Por lo tanto, si se aplica una escala de 3x a un elemento, es posible que veas barras de desplazamiento y otros elementos similares, incluso si el elemento está en 1x después de que se haya aplicado
perspective
. Es posible solucionar este problema si se ajustan los elementos desde la esquina inferior derecha (contransform-origin: bottom right
), lo que funciona porque hará que los elementos demasiado grandes crezcan hacia la "región negativa" (por lo general, la parte superior izquierda) del área desplazable. Las regiones desplazables nunca te permiten ver ni desplazarte al 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, esté vinculado al desplazamiento y sea compatible con varios navegadores. Dado que requiere un poco de manipulación matemática y una pequeña cantidad de código estándar para lograr el efecto deseado, creamos una pequeña biblioteca auxiliar y una muestra, que puedes encontrar en nuestro repositorio de GitHub de muestras de elementos de la IU.
Pruébalo y cuéntanos cómo te va.