Utilice animaciones basadas en desplazamiento para lograr direcciones de desplazamiento opuestas
A veces, las ideas tontas de un diseñador terminan siendo aceptadas por ti. Esto me pasó a mí, tuve que crear una columna de elementos que se movían en la dirección opuesta cuando el usuario se desplazaba por la página.
notas: Esta demostración sigue configuraciones de movimiento reducido, por lo que debe habilitar el movimiento para ver el efecto. Mientras escribo esto, estamos trabajando en la compatibilidad con Chrome y Safari.
Gracias a las funciones CSS modernas, esto no es tan difícil como podría pensar, especialmente animación impulsada por desplazamiento. ¡No sólo eso, sino que es divertido de hacer! Déjame mostrarte cómo lo solucioné; tal vez quieras compartir cómo lo habrías hecho de otra manera.
lenguaje de marcado de hipertexto
HTML consta de elementos principales (.opposing-columns), sus hijos (.opposing-column), y los hijos de sus hijos (.opposing-item):
<div class="opposing-columns">
<!-- Column 1 -->
<div class="opposing-column">
<div class="opposing-item">...</div>
<div class="opposing-item">...</div>
<div class="opposing-item">...</div>
</div>
<!-- Column 2 -->
<div class="opposing-column">
<div class="opposing-item">...</div>
<div class="opposing-item">...</div>
<div class="opposing-item">...</div>
</div>
<!-- Column 3 -->
<div class="opposing-column">
<div class="opposing-item">...</div>
<div class="opposing-item">...</div>
<div class="opposing-item">...</div>
</div>
</div>
Eso es todo lo que necesitamos en el marcado. ¡CSS hará el resto!
Establecer el estilo del contenedor principal.
Primero, lo configuraremos para que este efecto solo funcione en pantallas más grandes; no tiene sentido admitir este tipo de efecto en pantallas más pequeñas, ya que necesitaríamos espacio adicional para implementar el efecto.
/* Just on larger screens */
@media screen and (width >= 50rem) {
.opposing-columns {
display: flex;
gap: 2rem;
max-inline-size: min(90dvi, 50rem);
margin-inline: auto;
}
}
Establecer el efecto de “enmascaramiento”
Necesitamos hacer más con el contenedor principal para crear la ilusión de elementos en cada contenedor. .opposing-column Desaparecen cuando pasan. Los elementos de las columnas exteriores se mueven hacia arriba a medida que se desplaza y los elementos de la columna central se mueven hacia abajo. Cuando cruzan los límites de los padres, queremos que desaparezcan.
Entonces, vamos a hacer algunas cosas. Primero, estableceremos una variable de color de fondo en todo el documento:
@media screen and (width >= 50rem) {
:root {
--opposing-bg: lightcyan;
background-color: var(--opposing-bg);
}
.opposing-columns {
/* same styles as before */
}
}
En segundo lugar, aplicaremos el mismo color de fondo al padre. :before y :after Pseudoelementos:
@media screen and (width >= 50rem) {
:root {
--opposing-bg: lightcyan;
background-color: var(--opposing-bg);
}
.opposing-columns {
/* same styles as before */
&:before,
&:after {
content: "";
position: absolute;
inset-inline: 0;
block-size: calc(var(--opposing-mask) * 3);
pointer-events: none;
z-index: 1;
}
}
}
Tenga en cuenta que hemos creado un contexto de apilamiento en los pseudoobjetos y los configuramos un nivel por encima del padre y sus descendientes. Esta es la clave para enmascarar los elementos de cada columna a medida que se desplaza dentro y fuera del contenedor. Estos elementos son técnicamente deslizantes. abajo Pseudo máscara.
Hablando de eso, creemos otra variable llamada --opposing-mask Esto agrega espacio vertical entre el elemento principal y las tres columnas:
@media screen and (width >= 50rem) {
:root {
--opposing-bg: lightcyan;
--opposing-mask: 3rem;
background-color: var(--opposing-bg);
}
.opposing-columns {
display: flex;
gap: 2rem;
max-inline-size: min(90dvi, 50rem);
margin-inline: auto;
margin-block: var(--opposing-mask, 3rem);
position: relative;
}
}

Hagamos lo mismo con el pseudoobjeto padre, solo apliquemos --opposing-mask para ellos block-size Múltiplos de tres. De esta manera, hay un espacio vertical adicional entre ellos y sus padres.
@media screen and (width >= 50rem) {
:root {
--opposing-bg: lightcyan;
--opposing-mask: 3rem;
background-color: var(--opposing-bg);
}
.opposing-columns {
/* same styles as before */
&:before,
&:after {
content: "";
position: absolute;
inset-inline: 0;
block-size: calc(var(--opposing-mask) * 3);
pointer-events: none;
z-index: 1;
}
}
}

Quizás entiendas hacia dónde va esto. Tenemos un gran espacio entre el contenedor principal y su pseudocontenedor. Queremos que los elementos de la columna parezcan desvanecerse cuando se desplazan fuera del contenedor principal. No tenemos que alterar su opacidad ni nada por el estilo. En su lugar, podemos agregar un degradado de fondo encima del artefacto.
este :before El pseudo está en la parte superior del contenedor, por lo que le daremos un degradado desde un color sólido que coincida con el color de fondo subyacente del archivo hasta transparente, de arriba a abajo. y desde :after El pseudo está ubicado en la parte inferior del contenedor principal e invertiremos el degradado para que sea transparente al color de fondo del archivo de abajo hacia arriba.
@media screen and (width >= 50rem) {
:root {
/* same styles as before */
}
.opposing-columns {
/* same styles as before */
&:before,
&:after {
/* same styles as before */
}
&:before {
background-image: linear-gradient(
to bottom,
var(--opposing-bg) var(--opposing-mask),
transparent
);
inset-block-start: calc(var(--opposing-mask) * -1);
}
&:after {
background-image: linear-gradient(
to top,
var(--opposing-bg) var(--opposing-mask),
transparent
);
inset-block-end: calc(var(--opposing-mask) * -1);
}
}
}
}
diseño de columnas
Antes de empezar a trabajar con la magia, debemos distribuir los elementos en cada columna. Cada campo es un elemento Flex en su padre, que es un contenedor Flex. Los haremos más pequeños (flex-shrink: 1) y crecer (flex-grow: 1), limitando el tamaño en un punto determinado (flex-basis: 10rem).
Podemos definirlos todos de la siguiente manera flex Atributos abreviados:
@media screen and (width >= 50rem) {
/* same styles as before */
.opposing-column {
flex: 1 1 10rem;
}
}
Ahora quiero que estas columnas sean contenedores de cuadrícula para poder usar gap Propiedades para insertar espacios entre elementos:
@media screen and (width >= 50rem) {
/* same styles as before */
.opposing-column {
flex: 1 1 10rem;
display: grid;
gap: 2rem;
}
}
Podemos usar Flexbox aquí para acceder gappero el diseño predeterminado está configurado en row tenemos que reescribirlo column. En este caso, la rejilla está más limpia.
¡Animación!
Para eso viniste, ¿verdad? Hemos configurado todo para que los elementos de la columna entren y salgan del contenedor principal al desplazarse. Ahora necesitamos agregar un comportamiento de desplazamiento.
esto es animation-timeline La propiedad es realmente conveniente. Normalmente, las animaciones CSS se ejecutan solas. Comienza cuando se carga la página (o después de un retraso específico que usted establezca) y finaliza después de una duración que usted establezca. y animation-timelinele decimos a la animación que se ejecute según su posición de desplazamiento… de ahí el término animación “controlada por desplazamiento”.
Tenemos dos funciones compatibles aquí, scroll() y view(). Están relacionados pero muy diferentes. scroll() Ejecutar animaciones basadas en el desplazamiento del elemento. Ubicación. este view() Funciona de manera similar, pero rastrea el progreso de los elementos a medida que entran y salen. puerto de desplazamiento (es decir, el área desplazable del contenedor en el que se encuentra).
trabajaremos con view() funciona porque lo hemos configurado para tener un área desplazable clara dentro del contenedor principal. Necesitamos ejecutar la animación según la posición de entrada y salida del área en lugar de la posición de desplazamiento del elemento de la columna.
Esto es realmente interesante porque podemos decir view() ¿Dónde queremos que comience la animación? Ingresar Área desplazable y dónde detenerse después de desplazarse abandonar misma zona. Como esto:
/* Official syntax */
animation-timeline: view(( <axis> || <'view-timeline-inset'>)?);
Empecemos por definir los ejes:
@media screen and (width >= 50rem) {
/* same styles as before */
.opposing-column {
/* ... */
animation-timeline: view();
animation-range: entry cover;
}
}
Esto es sólo una parte de lo que queremos, pero lo que estamos diciendo es que queremos que la animación (1) comience en el momento en que se ingresa al puerto de desplazamiento (entry), (2) cuando abandona completamente el área (cover). Necesitamos ser explícitos sobre el recuadro, ya que determina el alcance de la animación en relación con sus posiciones de entrada y salida. Queremos la gama completa, así que entry Empezó con 0% La salida es cuando un proyecto es coverEditado en 100%.
@media screen and (width >= 50rem) {
/* same styles as before */
.opposing-column {
/* ... */
animation-timeline: view();
animation-range: entry 0% cover 100%;
}
}
Finalmente, configuramos la animación para que se ejecute linealmente, sin necesidad de ralentizarla o ralentizarla a medida que se desplaza el proyecto.
@media screen and (width >= 50rem) {
/* same styles as before */
.opposing-column {
/* ... */
animation-timing-function: linear;
animation-timeline: view();
animation-range: entry 0% cover 100%;
}
}
Vale, genial. Pero lo que no hemos hecho todavía es crear animaciones. Hemos configurado lo que hará cuando se ejecute, pero necesitamos definir el movimiento real.
Quiero configurar tres animaciones CSS separadas:
- Uno que traduce (mueve) los elementos de la primera columna hacia arriba.
- Una animación opuesta a la primera animación de un elemento en otra columna.
Técnicamente, podríamos animar la primera en las dos columnas exteriores, pero quiero que la tercera animación esté un poco desplazada de la primera para que las columnas parezcan escalonadas.
@keyframes scroll1 {
from { transform: translateY(var(--opposing-mask)); }
to { transform: translateY(calc(var(--opposing-mask) * -1)); }
}
@keyframes scroll2 {
from { transform: translateY(calc(var(--opposing-mask) * -1)); }
to { transform: translateY(var(--opposing-mask)); }
}
@keyframes scroll3 {
from { transform: translateY(calc(var(--opposing-mask) * .66)); }
to { transform: translateY(calc(var(--opposing-mask) * -.33)); }
}
Por supuesto, si necesitamos actualizarlos, podemos crear variables para ellos:
@media screen and (width >= 50rem) {
:root {
--opposing-bg: lightcyan;
--opposing-mask: 3rem;
--animation-1: scroll1;
--animation-2: scroll2;
--animation-3: scroll3;
/* ... */
}
}
…y aplicarlos a cada columna:
@media screen and (width >= 50rem) {
/* same styles as before */
.opposing-column {
/* same styles as before */
}
:where(.opposing-column:nth-of-type(1)) {
animation-name: var(--animation-1);
}
:where(.opposing-column:nth-of-type(2)) {
animation-name: var(--animation-2);
}
:where(.opposing-column:nth-of-type(3)) {
animation-name: var(--animation-3);
}
}
Cuando hagamos esto deberíamos desactivar la animación. Respetar la configuración de los usuarios para reducir el movimiento. (Y quítate la máscara o podría verse raro):
@media (prefers-reduced-motion: reduce) {
.opposing-column {
animation: unset;
&:before,
&:after {
content: unset;
}
}
}
Resumir
Entonces, sí, las animaciones basadas en desplazamiento son realmente geniales. Mientras escribo esto, todavía estamos esperando la compatibilidad con Firefox, pero ciertamente puedes incluirlo en @supports Proporcione una experiencia predeterminada mediante anotaciones de desplazamiento y luego configure una experiencia alternativa para navegadores no compatibles, como la ejecución en una línea de tiempo de animación normal:
@supports (animation-timeline: view()) {
/* ... */
}
Por supuesto, esto es sólo un primer vistazo a la animación basada en desplazamiento. ¿Qué tipo de cosas has hecho o probado? ¿O habrías manejado esto de manera diferente? ¡Hágamelo saber!