D
P
0
← Semua artikel Read in English

JavaScript, DOM & Animasi Browser

Zona Longgar plus `onToggle isActive`: Aturan ScrollTrigger yang Tersisa Tanpa Catatan Bug-nya

· · 6 menit baca
Zona Longgar plus `onToggle isActive`: Aturan ScrollTrigger yang Tersisa Tanpa Catatan Bug-nya

Ada satu mekanisme kecil di sebuah situs klien yang saya kerjakan Juli lalu. Header duduk fixed di paling atas halaman, dan dia punya satu class yang membalik warnanya selama dia menumpang di atas hero yang berisi video, lalu balik ke warna gelap setelah hero lewat. Class itu dipasang dan dilepas oleh ScrollTrigger.

Mekanisme itu sudah tidak ada lagi di kode situs itu sekarang. Yang tersisa satu baris aturan di catatan projek saya, dan aturan itu ditulis di bawah judul daftar yang isinya khusus kesalahan yang tidak boleh diulang.

Jadi tulisan ini bukan cerita "saya kena bug ini lalu memperbaikinya". Yang benar-benar bisa saya tunjukkan cuma dua: bunyi aturannya, dan bentuk kode yang akhirnya dikirim.

Aturannya memang cuma satu baris

Batas ScrollTrigger eksak: pakai zona longgar ('top bottom', end +headerHeight) + onToggle isActive, bukan onLeave/onEnterBack di batas persis.

Itu utuh. Tidak ada kalimat lain yang menemaninya, tidak ada deskripsi gejala sama sekali. Judul daftarnya sendiri berbunyi "Bug yang pernah muncul (jangan diulang)". Artinya saya tahu ada sesuatu yang pernah rusak, saya tahu bentuk perbaikan yang saya putuskan, dan saya tidak tahu lagi apa yang sebenarnya terlihat di layar waktu itu.

Yang bikin makin tipis: projek yang sama punya berkas terpisah khusus jebakan GSAP, isinya butir-butir seperti glitch video di Windows dan tween persen yang jadi dobel, dan bug batas eksak ini tidak disebut di sana sama sekali.

Belakangan ada catatan turunan yang mengisi kekosongan itu, lengkap dengan gejala (logo berubah hitam padahal masih di tengah hero) dan sebab akar (callback meleset di sekitar pembulatan piksel dan refresh). Dua-duanya cuma ada di catatan turunan itu dan berstatus dugaan penulisnya, bukan sesuatu yang tercatat waktu kejadian. Satu frasa yang dia kutip seolah-olah berasal dari catatan asli malah tidak ketemu sama sekali waktu saya cari di sana. Versi bermasalahnya pun tidak pernah masuk git: kemunculan pertama class ini di riwayat sudah berbentuk zona longgar, jadi tidak ada diff yang bisa saya adu.

Saya biarkan bagian itu apa adanya. Dugaan boleh jadi benar, tapi dugaan yang ditulis seperti temuan justru yang bikin catatan lama berhenti bisa dipercaya.

Bentuk yang benar-benar dikirim

var headerHeight = header.offsetHeight || 72;
 
var hero = document.querySelector('.hero');
if (hero) {
    header.classList.add('on-hero');
 
    ScrollTrigger.create({
        trigger: hero,
        start: 'top bottom',
        end: 'bottom top+=' + headerHeight,
        onToggle: function (self) {
            header.classList.toggle('on-hero', self.isActive);
        }
    });
}

Ada tiga hal yang menurut saya layak dilihat di sana.

headerHeight tidak dipatok, dia diambil dari tinggi header yang terukur saat itu, dengan 72 sebagai nilai cadangan kalau hasil pengukurannya nol. Ujung zonanya lalu ditulis relatif ke angka itu, bottom top+=headerHeight, bukan ke satu batas eksak.

Class-nya dipasang manual lebih dulu, sebelum triggernya dibuat. Jadi keadaan awal halaman sudah benar tanpa menunggu event scroll apa pun datang.

Lalu class-nya ditulis dari self.isActive di dalam onToggle. Argumen kedua classList.toggle menerima boolean, jadi tiap kali callback ini jalan, class-nya ditulis ulang dari kondisi trigger saat itu juga, bukan diakumulasi dari urutan kejadian sebelumnya.

Warnanya diwariskan, animasinya milik CSS

.page-header {
    position: fixed;
    inset: 0 0 auto;
    z-index: var(--layer-header);
    color: var(--fg-dark);
    transition: color 0.5s var(--ease);
}
 
/* Light over the hero video, dark once past it */
.page-header.on-hero { color: var(--fg-light); }
 
.logo-link { display: block; color: inherit; }

Warna dasar headernya gelap, dan pergantiannya dianimasikan oleh transition CSS setengah detik, bukan oleh tween GSAP. Class-nya sendiri cuma menyentuh satu properti dan panjangnya satu baris. Pembungkus logonya memakai color: inherit, dan berkas SVG lockup-nya memang dibuat dengan fill="currentColor" di elemen svg akarnya. Dua warna yang dipertukarkan cuma dua, #1C1C1C dan #FFFFFF.

Artinya ScrollTrigger di sini tidak menganimasikan apa-apa. Dia cuma menyalakan dan mematikan satu class, dan seluruh perpindahan warnanya diurus CSS.

Aturannya tidak disapu ke seluruh berkas

Di fungsi yang sama masih ada satu toggle lagi, untuk section gelap, dan toggle itu tetap memakai onEnter, onEnterBack, onLeave, dan onLeaveBack. Kalau aturannya dibaca sebagai "jangan pakai callback tepi", kode saya sendiri melanggarnya di baris berikutnya.

Bacaan yang benar ada di ekor kalimat aturannya: "di batas persis". Toggle kedua itu batasnya juga bukan batas eksak. Start dan end-nya berbentuk fungsi yang menggeser titiknya sejauh setengah tinggi header.

Masih di berkas yang sama, ada satu toggle header lain lagi yang bahkan tidak menyentuh ScrollTrigger sama sekali, cuma listener scroll biasa dengan ambang 8 piksel. Dan butir tetangganya di daftar bug yang sama bernada serupa: jangan pernah pasang interval atau safety-net yang menyentuh properti yang sedang dianimasikan ScrollTrigger.

Konteks yang sudah tidak berlaku lagi

Waktu mekanisme ini hidup, halamannya masih memakai Lenis, jadi ScrollTrigger di situ tidak bekerja di atas scroll native. Lenis-nya kemudian dicabut total dari projek karena bundle 1.1.6 melempar "i is not a constructor" di Chrome 150. Kalau saya memindahkan aturan ini ke projek lain, itu yang harus saya ingat: dia lahir di halaman yang punya lapisan smooth scroll, dan lapisan itu sekarang sudah tidak ada di situsnya sendiri.

Umurnya enam hari

Mekanisme pergantian warna logo ini masuk 11 Juli 2026 di versi tema 1.6.8, dan pesan commitnya menyebutnya dynamic logo color over hero. Dia dicabut 17 Juli 2026 di versi 1.8.4, dan di HEAD sekarang tidak ada sisanya sama sekali.

Yang mencabutnya bukan bug. Klien justru memuji mekanisme ini dengan sebutan "Genius idea", dan dia masuk daftar yang tidak boleh dihapus. Dia juga bagian dari opsi scroll yang klien pilih sendiri dari dua opsi yang ditawarkan. Yang mencabutnya permintaan klien untuk menghapus seluruh animasi scroll, yang terekam verbatim begini: "remove the animation, no need to scroll or anything, just one page and the footer smaller and visible immediately". Catatan status projek menyebut fungsi theme swap itu dengan nama di daftar fungsi yang dihapus, dan header di halaman depan sekarang disembunyikan lewat CSS.

Yang saya bawa dari catatan ini