Cómo reproducir y pausar animaciones CSS con propiedades personalizadas de CSS | Programar Plus

Echemos un vistazo a CSS @keyframes animaciones, y específicamente sobre cómo puedes pausa y de lo contrario controlarlos. Hay una propiedad CSS específica para él, que se puede controlar con JavaScript, pero hay muchos matices para entrar en los detalles. También veremos mi forma preferida de configurar esto, que brinda mucho control. Pista: se trata de propiedades personalizadas de CSS.

La importancia de pausar animaciones

Recientemente, mientras trabajaba en la presentación de diapositivas impulsada por CSS que verá más adelante en este artículo, estaba inspeccionando las animaciones en el Capas panel de DevTools. Noté algo interesante en lo que nunca había pensado antes: ¡las animaciones que no están actualmente en la ventana gráfica todavía se están ejecutando!

Quizás no sea tan inesperado. Sabemos que los videos hacen eso. Los videos continúan hasta que los pause. Pero me hizo preguntarme si estas animaciones de reproducción todavía usan la CPU / GPU. ¿Consumen potencia de procesamiento innecesaria, lo que ralentiza otras partes de la página?

Inspeccionar marcos en el Rendimiento El panel en DevTools no arrojó más luz sobre esto ya que no podía ver los fotogramas “fuera de la pantalla”. Pero, cuando me desplacé fuera de mi “Presentación de diapositivas solo CSS” en la primera diapositiva, luego esperé y volví a desplazarme hacia atrás, estaba en la diapositiva cinco. La animación no se había detenido. Las animaciones simplemente se ejecutan y se ejecutan, hasta que las pausas.

Entonces comencé a investigar cómo, por qué y cuándo deberían detenerse las animaciones. El rendimiento es una razón obvia, dados los hallazgos anteriores. Otra razón es el control. A los usuarios no solo les encanta tener el control, sino que también deben tener el control. Hace un par de años, mi esposa tuvo una conmoción cerebral muy grave. Desde entonces, ha evitado las páginas web con demasiadas animaciones, ya que la marean. Como resultado, considero que la accesibilidad es quizás la razón más importante para permitir que las animaciones se detengan.

Todos juntos, esto es algo importante. Estamos hablando específicamente de animaciones de fotogramas clave CSS, pero en términos generales, eso significa que estamos hablando de:

  1. Rendimiento
  2. Control
  3. Accesibilidad

Los conceptos básicos para pausar una animación

La única forma de pausar realmente una animación en CSS es usar el animation-play-state propiedad con un paused valor.

.paused {
  animation-play-state: paused;
}

En JavaScript, la propiedad es “camelCased” como animationPlayState y establecer así:

element.style.animationPlayState="paused";

Podemos crear un conmutador que reproduzca y pause la animación leyendo el valor actual de animationPlayState:

const running = element.style.animationPlayState === 'running';

… y luego configurándolo en el valor opuesto:

element.style.animationPlayState = running ? 'paused' : 'running';

Establecer la duración

Otra forma de pausar animaciones es configurar animation-duration a 0s. La animación se está ejecutando, pero como no tiene duración, no verá ninguna acción.

Pero si cambiamos el valor a 3s en lugar de:

Funciona, pero tiene una advertencia importante: las animaciones técnicamente aún se están ejecutando. La animación simplemente alterna entre su posición inicial y el siguiente lugar de la secuencia.

Directamente quitando la animación

Podemos eliminar la animación por completo y volver a agregarla a través de clases, pero como animation-duration, esto en realidad no detiene la animación.

.remove-animation {
  animation: none !important;
}

Dado que la verdadera pausa es realmente lo que buscamos aquí, sigamos con animation-play-state y busque otras formas de usarlo.

Uso de atributos de datos y propiedades personalizadas de CSS

Usemos un atributo de datos como selector en nuestro CSS. Podemos llamarlos como queramos, así que voy a usar un [data-animation]-atributo en todos los elementos donde me gustaría reproducir / pausar animaciones. De esa forma, se puede distinguir de otras animaciones:

<div data-animation></div>

Ese atributo es el selector, y el animation La taquigrafía es la propiedad donde estamos configurando todo. Agregaremos un montón de propiedades personalizadas de CSS * (* usando abreviaturas de Emmet) como valores:

[data-animation] {
  animation:
    var(--animn, none)
    var(--animdur, 1s)
    var(--animtf, linear)
    var(--animdel, 0s)
    var(--animic, infinite)
    var(--animdir, alternate)
    var(--animfm, none)
    var(--animps, running);
}

Con eso en su lugar, cualquier animación con este atributo de datos estará perfectamente lista para aceptar animaciones, y podemos controlar aspectos individuales de la animación con propiedades personalizadas. Algunas animaciones tendrán algo en común (como duración, tipo de aceleración, etc.), por lo que los valores de reserva también se establecen en las propiedades personalizadas.

¿Por qué propiedades personalizadas de CSS? En primer lugar, se pueden leer y configurar tanto en CSS como en JavaScript. En segundo lugar, ayudan a reducir significativamente la cantidad de CSS que necesitamos escribir. Y, dado que podemos establecerlos dentro @keyframes (al menos en Chrome al momento de escribir este artículo), ¡ofrecen formas nuevas y emocionantes de trabajar con animaciones!

Para las animaciones en sí, estoy usando selectores de clases y actualizo las variables del [data-animation]-selector:

<div class="circle a-slide" data-animation></div>

¿Por qué una clase y un atributo de datos? En esta etapa, el data-animation El atributo bien podría ser una clase normal, pero lo usaremos de formas más avanzadas más adelante. Tenga en cuenta que el .circle el nombre de la clase en realidad no tiene nada que ver con la animación, es solo una clase para diseñar el elemento.

/* Animation classes */
.a-pulse {
  --animn: pulse;
}
.a-slide {
  --animdur: 3s;
  --animn: slide;
}

/* Keyframes */
@keyframes pulse {
  0% { transform: scale(1); }
  25% { transform: scale(.9); }
  50% { transform: scale(1); }
  75% { transform: scale(1.1); }
  100% { transform: scale(1); }
}
@keyframes slide {
  from { margin-left: 0%; }
  to { margin-left: 150px; }
}

Solo necesitamos actualizar los valores que cambiarán, por lo que si usamos algunos valores comunes en los valores de respaldo para el data-animation selector, solo necesitamos actualizar el nombre de la propiedad personalizada de la animación, --animn.

Ejemplo: pausar con el truco de la casilla de verificación

Para pausar todas las animaciones usando el truco de la casilla de verificación, creemos una casilla de verificación antes de las animaciones:

<input type="checkbox" data-animation-pause />

Y actualiza el --animps propiedad cuando checked:

[data-animation-pause]:checked ~ [data-animation] {
  --animps: paused;
}

¡Eso es! Las animaciones alternan entre reproducidas y pausadas al hacer clic en la casilla de verificación; no se requiere JavaScript.

Presentación de diapositivas solo CSS

¡Pongamos en práctica algunas de estas ideas!

He jugado con el <details>-etiquetar mucho recientemente. Es el candidato obvio para los acordeones, pero también se puede usar para información sobre herramientas, consejos de alternancia, menús desplegables (con estilo <select>-look-a-likes), mega-menús … lo que sea. Después de todo, es el elemento oficial de divulgación HTML. Aparte de los atributos globales y eventos globales que aceptan todos los elementos HTML, <details> tiene un solo open atributo, y un solo toggle evento. Entonces, al igual que el truco de la casilla de verificación, es perfecto para alternar el estado, pero aún más simple:

details[open] {
  --state: 1;
}
details:not([open]) {
  --state: 0;
}

Decidí hacer una presentación de diapositivas, donde las diapositivas cambian automáticamente a través de una animación primaria llamada autoplay, y cada diapositiva individual tiene su propia animación secundaria única. El animation-play-state es controlado por el --animps-propiedad. Cada diapositiva individual puede tener su propia animación única, definida en un --animn-propiedad:

<figure style="--animn:kenburns-top;--index:0;">
  <img src="https://css-tricks.com/how-to-play-and-pause-css-animations-with-css-custom-properties/some-slide-image.jpg" />
  <figcaption>Caption</figcaption>
</figure>

El animation-play-state de las animaciones secundarias están controladas por el --img-animps-propiedad. Encontré un montón de bonitas animaciones al estilo de Ken Burns en Animista y cambié entre ellas en el --animn-propiedades de las diapositivas.

Pausar una animación de otra animación

Para evitar la sobrecarga de la GPU, sería ideal que la animación principal hiciera una pausa en las animaciones secundarias. Lo notamos brevemente antes, pero solo Chrome (en el momento de escribir este artículo, y es un poco inestable) puede actualizar una propiedad personalizada de CSS desde un @keyframe animación – que puede ver en el siguiente ejemplo donde el --bgc-propiedad y --counter-las propiedades se modifican en diferentes marcos:

El estado inicial de la animación secundaria, el --img-animps -propiedad, debe ser paused, incluso si la animación principal se está ejecutando:

details[open] ~ .c-mm__inner .c-mm__frame {
  --animps: running;
  --img-animps: paused;
}

Luego, en la animación principal @keyframes, la propiedad se actualiza a running:

@keyframes autoplay {
  0.1% {
    --img-animps: running; /* START */
    opacity: 0;
    z-index: calc(var(--z) + var(--slides))
  }
  5% { opacity: 1 }
  50% { opacity: 1 }
  51% { --img-animps: paused } /* STOP! */
  100% {
    opacity: 0;
    z-index: var(--z)
  }
}

Para que esto funcione en navegadores distintos de Chrome, el valor inicial debe ser running, ya que no pueden actualizar una propiedad personalizada de CSS desde un @keyframe.

Aquí está la presentación de diapositivas, con un botón de reproducción / pausa de “truco de detalles”, no se requiere JavaScript:

Habilitar prefers-reduced-motion

Algunas personas prefieren la ausencia de animaciones o, al menos, el movimiento reducido. Puede ser solo una preferencia personal, pero también puede deberse a una condición médica. Hablamos sobre la importancia de la accesibilidad con animaciones en la parte superior de esta publicación.

Tanto macOS como Windows tienen opciones que permiten a los usuarios informar a los navegadores que prefieren un movimiento reducido en los sitios web. Esto nos permite alcanzar la prefers-reduced-motion consulta de características, sobre la que Eric Bailey ha escrito todo.

@media (prefers-reduced-motion) { ... }

Usemos el [data-animation]-selector de movimiento reducido dándole diferentes valores que se aplican cuando prefers-reduced-motion está habilitado*:*

  • alternate = ejecutar una animación diferente
  • once = establecer el animation-iteration-count a 1
  • slow = cambiar el animation-duration-propiedad
  • stop = conjunto animation-play-state a paused

Estas son solo sugerencias y pueden ser lo que quieras, de verdad.

<div class="circle a-slide" data-animation="alternate"></div>
<div class="circle a-slide" data-animation="once"></div>
<div class="circle a-slide" data-animation="slow"></div>
<div class="circle a-slide" data-animation="stop"></div>

Y la consulta de medios actualizada:

@media (prefers-reduced-motion) {
  [data-animation="alternate"] {
   /* Change animation duration AND name */
    --animdur: 4s;
    --animn: opacity;
  }
  [data-animation="slow"] {
    /* Change animation duration */
    --animdur: 10s;
  }
  [data-animation="stop"] {
    /* Stop the animation */
    --animps: paused;
  }
}

Si esto es demasiado genérico y prefiere tener animaciones alternativas únicas por clase de animación, agrupe los selectores de esta manera:

.a-slide[data-animation="alternate"] { /* etc. */ }

Aquí hay un bolígrafo con una casilla de verificación que simula prefers-reduced-motion. Desplácese hacia abajo dentro del lápiz para ver el cambio de comportamiento de cada círculo:

Pausar con JavaScript

Para volver a crear la casilla de verificación “Pausar todas las animaciones” en JavaScript, repita todas las [data-animation]-elementos y alternar lo mismo --animps propiedad personalizada:

<button id="js-toggle" type="button">Toggle Animations</button>
animaciones constantes = document.querySelectorAll ('[data-animation');
const jstoggle = document.getElementById('js-toggle');

jstoggle.addEventListener('click', () => {
  animations.forEach(animation => {
    const running = getComputedStyle(animation).getPropertyValue("--animps") || 'running';
    animation.style.setProperty('--animps', running === 'running' ? 'paused' : 'running');
  })
});

It’s exactly the same concept as the checkbox hack, using the same custom property: --animps, only set by JavaScript instead of CSS. If we want to support older browsers, we can toggle a class, that will update the animation-play-state.

Using IntersectionObserver

To play and pause all [data-animation]-animaciones automáticamente – y por lo tanto sin sobrecargar innecesariamente la GPU – podemos usar un IntersectionObserver.

Primero, debemos asegurarnos de que no se estén ejecutando animaciones:

[data-animation] {
  /* Change 'running' to 'paused' */
  animation: var(--animps, paused); 
}

Luego, crearemos el observador y lo activaremos cuando un elemento esté al 25% o 75% en la ventana gráfica. Si este último coincide, la animación comienza a reproducirse; de lo contrario, se detiene.

De forma predeterminada, todos los elementos con un [data-animation]-se observará el atributo, pero si prefers-reduced-motion está habilitado (establecido en “reducir”), los elementos con [data-animation="stop"] será ignorado.

const IO = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      const state = (entry.intersectionRatio >= 0.75) ? 'running' : 'paused';
      entry.target.style.setProperty('--animps', state);
    }
  });
}, {
  threshold: [0.25, 0.75]
});

const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
const elements = mediaQuery?.matches ? document.querySelectorAll(`[data-animation]:not([data-animation="stop"]`) : document.querySelectorAll('[data-animation]');

elements.forEach(animation => {
  IO.observe(animation);
});

Tienes que jugar con el threshold-valores, y / o si necesita unobserve algunas animaciones después de que se hayan activado, etc. Si carga contenido nuevo o animaciones de forma dinámica, es posible que también deba volver a escribir partes del observador. Es imposible cubrir todos los escenarios, pero usar esto como base debería ayudarlo a comenzar con la reproducción automática y la pausa de las animaciones CSS.

Bono: Agregar <audio> a la presentación de diapositivas con un mínimo de JavaScript

Aquí tienes una idea para agregar música a la presentación de diapositivas que creamos. Primero, agregue un audio-etiqueta:

<audio src="https://css-tricks.com/asset/audio/slideshow.mp3" hidden loop></audio>

Luego, en Javascript:

const audio = document.querySelector('your-audio-selector');
const details = document.querySelector('your-details-selector');
details.addEventListener('toggle', () => {
  details.open ? audio.play() : audio.pause();
})

Bastante simple, ¿eh?

Hice una demostración de “Película muda” (con audio) aquí, donde conoces mi pasado geek. 🙂