Gerakan

  • Gunakan clip-path dan mask-image untuk pengungkapan geometris kustom dan fade-out yang halus.
  • Gunakan Scroll-Driven Animations (animation-timeline: scroll()) untuk efek yang terikat scroll yang tidak penting, bukan pemroses JS.
  • Gunakan Transisi Tampilan untuk menganimasikan transisi antar-status tata letak yang kompleks dengan lancar.

Performa

  • Sebaiknya animasikan opacity dan transform (termasuk properti transformasi individual, misalnya translate, bukan left/right/top/bottom) untuk memastikan animasi tetap berada di thread compositor.
  • Gunakan transition-behavior: allow-discrete, @starting-style, dan (untuk elemen lapisan atas seperti <dialog> atau [popover]) overlay untuk menganimasikan status masuk dan keluar diskret secara native; lihat Menganimasikan Masuk dan Keluar Elemen dan Menganimasikan Elemen Ke dan Dari Lapisan Atas.
.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;
}

Aksesibilitas

Gunakan kueri media prefers-reduced-motion untuk menonaktifkan gerakan berat bagi pengguna yang lebih menyukainya.

  • Menyediakan mekanisme Jeda: Memungkinkan pengguna menghentikan carousel, banner, atau animasi persisten lainnya yang berjalan otomatis.
  • Default ke tampilan statis: Pertimbangkan untuk menetapkan status statis sebagai default dan mengizinkan pengguna memilih untuk mengaktifkan gerakan.
  • Jangan melebihi batas kedipan (tiga per detik): Jangan pernah menyertakan kedipan cepat dari terang ke gelap. Efek tersebut dapat menyebabkan kejang.

JANGAN menerapkan animation-duration: 0.01ms; secara global karena dapat menyebabkan animasi tertentu menjadi lebih mengganggu. Sebagai gantinya, tangani preferensi gerakan berdasarkan kasus per kasus menggunakan salah satu dari dua pendekatan, yaitu menyusun kueri @media di dalam aturan sehingga kedua versi tetap bersama:

  • Mulai dengan animasi yang diaktifkan secara default, dan kurangi intensitasnya menggunakan @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;
  }
}
  • Mulai tanpa animasi secara default, dan aktifkan menggunakan @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;
  }
}

Menggunakan peristiwa animasi dan transisi di JavaScript

Pada antarmuka AnimationEvent (animationstart, animationiteration, animationend, animationcancel) dan TransitionEvent (transitionrun, transitionstart, transitionend, transitioncancel), event.target memberi Anda elemen DOM asal. Untuk mengakses objek Animation tertentu (Animation, CSSAnimation, atau CSSTransition) yang memicu peristiwa, gunakan properti event.animation hanya baca.

Jika event.animation tidak tersedia, Anda biasanya dapat menemukan instance Animation dengan memanggil event.target.getAnimations() dan memfilter menurut event.animationName (atau event.propertyName untuk transisi). Namun, sebaiknya gunakan event.animation secara langsung, untuk menghindari beberapa skenario saat penggantian yang memfilter event.target.getAnimations() ini tidak berfungsi seperti yang diharapkan:

  • Animasi Web Animations API (element.animate()) tidak memiliki animationName, sehingga tidak dapat dicocokkan berdasarkan nama saat memfilter event.target.getAnimations().
  • Pemfilteran menurut animationName untuk AnimationEvent bersifat ambigu jika beberapa animasi dengan nama yang sama dilampirkan ke elemen yang sama.
    • Perhatikan bahwa pemfilteran pada transitionProperty untuk TransitionEvent berfungsi karena hanya ada satu peristiwa per properti transisi.
  • event.target selalu mengarah ke elemen DOM asal, sehingga tidak membedakan antara animasi yang berjalan pada elemen itu sendiri dan salah satu elemen semunya — sedangkan event.animation.effect.pseudoElement mengekspos elemen semu target secara langsung.
// 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);
});

Anda dapat mendeteksi fitur dukungan menggunakan 'animation' in AnimationEvent.prototype (atau 'animation' in TransitionEvent.prototype).