Movimiento

  • Usa clip-path y mask-image para las revelaciones geométricas personalizadas y las transiciones suaves.
  • Usa animaciones controladas por el desplazamiento (animation-timeline: scroll()) para los efectos no esenciales vinculados al desplazamiento en lugar de los objetos de escucha de JS.
  • Usa transiciones de vistas para animar entre estados de diseño complejos sin problemas.

Rendimiento

  • Se recomienda animar opacity y transform (incluidas las propiedades de transformación individuales, p.ej., translate en lugar de left/right/top/bottom) para garantizar que las animaciones permanezcan en el subproceso del compositor.
  • Usa transition-behavior: allow-discrete, @starting-style y (para elementos de la capa superior, como <dialog> o [popover]) overlay para animar estados de entrada y salida discretos de forma nativa. Consulta Anima la entrada y salida de elementos y Anima elementos hacia y desde la capa superior.
.popover-reveal {
  /* Transition discrete display and top-layer overlay alongside opacity */
  transition:
    opacity ease-out,
    display,
    overlay;
  transition-duration: 0.2s;
  transition-behavior: allow-discrete;
}

Accesibilidad

Usa las consultas de medios prefers-reduced-motion para desactivar el movimiento intenso para los usuarios que lo prefieran.

  • Proporciona un mecanismo de pausa: Permite que los usuarios detengan los carruseles, los banners o cualquier otra animación persistente que se ejecute automáticamente.
  • Usa vistas estáticas de forma predeterminada: Considera usar estados estáticos de forma predeterminada y permitir que los usuarios habiliten el movimiento.
  • No excedas los límites de destellos (tres por segundo): Nunca incluyas destellos rápidos de luz a oscuridad. Estos efectos pueden provocar convulsiones.

NO apliques animation-duration: 0.01ms; de forma global, ya que puede hacer que ciertas animaciones se vuelvan más discordantes. En su lugar, controla las preferencias de movimiento caso por caso con uno de los dos enfoques, anidando la consulta @media dentro de la regla para que ambas versiones se mantengan juntas:

  • Comienza con las animaciones habilitadas de forma predeterminada y atenúalas con @media (prefers-reduced-motion: reduce):
/* Good: Dampen spin states or slow down animations for reduced motion */
.spinner {
  animation: spin 1s linear infinite;
  @media (prefers-reduced-motion: reduce) {
    animation: none;
    opacity: 0.5;
  }
}

progress:not([value]) {
  animation: slide 1s infinite linear;
  @media (prefers-reduced-motion: reduce) {
    animation-duration: 20s;
  }
}
  • Comienza sin animaciones de forma predeterminada y habilítalas con @media (prefers-reduced-motion: no-preference):
/* Good: Default to static or gentle states and opt-in to full motion */
.spinner {
  opacity: 0.5;
  @media (prefers-reduced-motion: no-preference) {
    opacity: 1;
    animation: spin 1s linear infinite;
  }
}

progress:not([value]) {
  animation: slide 20s infinite linear;
  @media (prefers-reduced-motion: no-preference) {
    animation-duration: 1s;
  }
}

Cómo trabajar con eventos de animación y transición en JavaScript

En las interfaces AnimationEvent (animationstart, animationiteration, animationend, animationcancel) y TransitionEvent (transitionrun, transitionstart, transitionend, transitioncancel), event.target te proporciona el elemento DOM de origen. Para acceder al objeto Animation específico (Animation, CSSAnimation o CSSTransition) que activó el evento, usa la propiedad event.animation de solo lectura.

Cuando event.animation no está disponible, por lo general, puedes encontrar la instancia de Animation llamando a event.target.getAnimations() y filtrando por event.animationName (o event.propertyName para las transiciones). Sin embargo, se prefiere usar event.animation directamente para evitar varias situaciones en las que este resguardo que filtra event.target.getAnimations() no funciona como se espera:

  • Las animaciones de la API de Web Animations (element.animate()) no tienen un animationName, por lo que no se pueden hacer coincidir por nombre cuando se filtra event.target.getAnimations().
  • Filtrar por animationName para AnimationEvent es ambiguo cuando se adjuntan varias animaciones con el mismo nombre al mismo elemento.
    • Ten en cuenta que el filtrado en transitionProperty para TransitionEvent funciona, ya que solo hay un evento por propiedad de transición.
  • event.target siempre apunta al elemento del DOM de origen, por lo que no distingue entre las animaciones que se ejecutan en el elemento en sí y uno de sus seudoelementos, mientras que event.animation.effect.pseudoElement expone el seudoelemento de destino directamente.
// Works in most basic CSS animation cases, but fails for WAAPI animations,
// duplicate animation names on the same element, or pseudo-elements.
document.addEventListener('animationstart', (event) => {
  const animation = event.target
    .getAnimations()
    .find((anim) => anim.animationName === event.animationName);
  console.log(animation);
});

// PREFERRED: Directly access the exact Animation object via event.animation
document.addEventListener('animationstart', (event) => {
  const animation = event.animation;
  console.log(animation);
});

Puedes detectar la compatibilidad con funciones usando 'animation' in AnimationEvent.prototype (o 'animation' in TransitionEvent.prototype).