Header yang menyusut saat di-scroll

Header yang menyusut saat di-scroll adalah pola UI umum, yaitu elemen header tetap di bagian atas halaman bertransisi dengan lancar ke ukuran yang lebih kecil saat pengguna men-scroll ke bawah. Efek ini sering digunakan untuk memaksimalkan ruang layar untuk konten utama sekaligus menjaga elemen navigasi atau branding yang penting tetap dapat diakses. Dengan animasi berbasis scroll CSS, efek ini dapat dicapai secara deklaratif dan berperforma tinggi, dengan menautkan animasi ke posisi scroll dokumen.

Cara menerapkan

Berikut cara membuat header yang menyusut saat di-scroll:

  1. Buat header tetap: Mulailah dengan elemen header yang ditetapkan ke bagian atas halaman dan memiliki tinggi yang telah ditentukan.

    <header>HEADER</header>
    
    header {
      position: fixed;
      height: 200px;
      top: 0;
      left: 0;
      right: 0;
    }
    
  2. Tentukan animasi penyusutan: Buat animasi CSS yang mengubah tinggi header.

    @keyframes shrink {
      to {
        height: 50px;
      }
    }
    
  3. Terapkan animasi dan linimasa scroll: Lampirkan animasi ke header dan gunakan fungsi scroll() untuk menautkannya ke posisi scroll dokumen.

    header {
      animation: shrink auto linear both;
      animation-timeline: scroll(block root);
    }
    
  4. Menetapkan animation-range: Gunakan properti animation-range untuk menentukan jarak scroll tempat animasi akan terjadi. Misalnya, untuk memperkecil header pada 150 piksel pertama scroll, Anda akan menggunakan animation-range: 0px 150px;.

    header {
      animation-range: 0px 150px;
    }
    

Tips: Agar konten setelah header tidak tertutup oleh header, tambahkan padding-top ke body (atau penampung konten utama) yang sama dengan tinggi awal header.

Tips: Untuk memastikan konten halaman di-scroll sinkron dengan header yang menyusut, tetapkan animation-range-end ke perbedaan antara ukuran awal dan akhir. Hal ini memastikan animasi selesai tepat saat header mencapai ukuran akhirnya. Dalam demo ini, header menyusut dari 200px menjadi 50px, sehingga animation-range-end ditetapkan ke 150px.

Kode contoh

@keyframes shrink {
  to {
    height: 50px;
  }
}

header {
  animation: shrink auto linear both;
  animation-timeline: scroll(block root);
  animation-range: 0px 150px;
}

Praktik Terbaik

Saat menggunakan animasi berbasis scroll, penting untuk mengikuti beberapa praktik terbaik guna memastikan pengalaman yang lancar dan mudah diakses:

  • LAKUKAN penyertaan deteksi fitur: Tidak semua browser mendukung animasi berbasis scroll. Gunakan @supports ((animation-timeline: scroll()) and (animation-range: 0% 100%)) untuk memeriksa dukungan dan memberikan penggantian untuk browser yang tidak mendukungnya.
    • Pemeriksaan (animation-range: 0% 100%) HARUS disertakan di sini, untuk memfilter browser yang hanya memiliki dukungan parsial.
    • JANGAN gunakan paket scroll-timeline-polyfill untuk strategi penggantian karena fiturnya tidak lengkap dan memiliki banyak masalah umum.
    • Jika animasi hanya dianggap sebagai dekoratif, pilih Progressive Enhancement dan JANGAN berikan penggantian.
  • LAKUKAN penghormatan terhadap preferensi pengguna: Beberapa pengguna lebih memilih untuk memiliki lebih sedikit gerakan di web. Gunakan kueri media prefers-reduced-motion untuk menonaktifkan atau mengurangi animasi bagi pengguna ini.
  • LAKUKAN upaya untuk menganimasikan hanya properti CSS yang berperforma tinggi: Untuk animasi yang paling lancar, tetap gunakan properti animasi yang dapat ditangani oleh thread compositor browser, seperti transform dan opacity. Menganimasikan properti lain seperti width atau height dapat menyebabkan masalah performa.
  • LAKUKAN penggunaan urutan deklarasi yang benar: Saat menggunakan properti singkat animation, deklarasikan animation-timeline dan animation-range setelahnya untuk mencegah properti singkat mereset linimasa.

Saat menggunakan fungsi scroll() untuk membuat animasi berbasis scroll:

  • OPSIONAL tentukan scroller secara eksplisit: Jika tidak menargetkan scroller ancestor terdekat, tentukan scroller yang ingin Anda gunakan secara eksplisit dengan scroll(root) atau scroll(self).
    • Jika root, nearest, atau self tidak cukup, gunakan scroll-timeline bernama.
  • OPSIONAL jelaskan sumbu yang akan dilacak: Jika tidak menargetkan sumbu block default (seperti dalam scroller horizontal), jelaskan sumbu yang akan dilacak dengan scroll(block) atau scroll(inline).

Khusus untuk kasus penggunaan ini:

  • Elemen yang Anda animasikan HARUS bukan position: static atau position: relative saat menggunakan persentase di animation-range.
    • Hal ini karena elemen tersebut dianggap “dalam alur”. Memperkecil elemen tersebut saat Anda men-scroll akan memperkecil total jarak scroll, sehingga memengaruhi nilai yang dihitung — misalnya — 10% ke dalam scroll.

Dukungan browser dan strategi penggantian

Oleh karena itu, strategi penggantian biasanya diperlukan.

Untuk browser yang tidak mendukung animasi berbasis scroll, Anda dapat menggunakan penggantian untuk membuat ulang efek visual. Penggantian biasanya dibuat dengan pemroses scroll (untuk efek ScrollTimeline) atau IntersectionObserver API (untuk efek ViewTimeline).

Di browser dengan dukungan bawaan untuk animasi berbasis scroll, SELALU gunakan implementasi CSS native karena lebih berperforma.

Perhatikan bahwa tidak semua efek dapat dibuat ulang menggunakan pendekatan penggantian.

Khusus untuk kasus penggunaan ini, skrip berikut menerapkan penggantian untuk browser yang tidak mendukung animasi berbasis scroll. Kode ini menggunakan pemroses scroll untuk melacak posisi scroll dokumen pada jarak 150px dan memperbarui tinggi header.

// Fallback for browsers that don't support scroll-driven animations
if (!CSS.supports('(animation-timeline: scroll()) and (animation-range: 0% 100%)')) {
  const header = document.querySelector('header');

  const initialHeight = 200;
  const finalHeight = 50;
  const scrollDistance = 150;

  window.addEventListener('scroll', () => {
    const scrollY = window.scrollY;
    const scrollPercent = Math.min(1, scrollY / scrollDistance);
    const newHeight = initialHeight - (initialHeight - finalHeight) * scrollPercent;

    header.style.height = `${newHeight}px`;
  });
}