D
P
0

Web Animation

Section `pin: true` + `scrub` ScrollTrigger Patah-Patah di Mobile Padahal Desktop Mulus? Lenis dan URL Bar Berebut Scroll

4 Juli 2026·4 menit baca
Section `pin: true` + `scrub` ScrollTrigger Patah-Patah di Mobile Padahal Desktop Mulus? Lenis dan URL Bar Berebut Scroll

Sebuah landing page yang saya kerjakan punya satu bagian andalan: hero sequence yang di-pin dengan GSAP ScrollTrigger dan di-scrub mengikuti scroll, dibungkus Lenis supaya seluruh halaman terasa smooth dan sinematik. Setup-nya standar, plumbing integrasinya juga sudah sesuai anjuran resmi:

const lenis = new Lenis();
 
lenis.on('scroll', ScrollTrigger.update);
 
gsap.ticker.add((time) => {
  lenis.raf(time * 1000);
});
 
gsap.ticker.lagSmoothing(0);

Dan section hero-nya kurang lebih begini:

gsap.timeline({
  scrollTrigger: {
    trigger: '.hero-sequence',
    start: 'top top',
    end: '+=300%',
    pin: true,
    scrub: 1,
  },
});

Di desktop, hasilnya sutra. Scroll pelan, sequence bergerak halus, tidak ada satu frame pun yang jatuh. Lalu saya buka di HP. Section yang di-pin itu bergetar. Elemen yang seharusnya diam terkunci di viewport malah kelihatan stutter, kadang bounce beberapa piksel, paling parah di tengah-tengah scrub. Bukan lambat, bukan lag, tapi jitter: gerakan kecil yang salah arah, berulang, dan sangat kelihatan justru karena elemennya seharusnya tidak bergerak sama sekali.

Investigasi

Refleks pertama saya menyalahkan performa. Saya kecilkan gambar, saya matikan efek lain, saya cek panel Performance. Frame rate-nya baik-baik saja. Ini bukan halaman yang berat, ini halaman yang bertengkar dengan dirinya sendiri.

Setelah membedah frame demi frame, ketemu dua penyebab yang saling menumpuk.

Pertama, masalah dua sopir. Lenis memvirtualisasi scroll: dia mendengar input, lalu menggerakkan halaman lewat loop rAF miliknya sendiri dengan smoothing. Sementara itu, ScrollTrigger melakukan pin dengan cara mereposisi elemen terhadap posisi scroll yang sesungguhnya. Di desktop dengan mouse wheel, keduanya hampir selalu sepakat. Di mobile, sentuhan jari, smoothing Lenis, dan reposisi pin bisa berselisih satu frame di sana-sini. Selisihnya sub-piksel, tapi di elemen yang seharusnya diam, selisih sub-piksel yang bolak-balik itu terbaca oleh mata sebagai getaran.

Kedua, URL bar browser mobile. Saat kamu scroll di HP, browser menyembunyikan dan memunculkan kembali URL bar, dan setiap kali itu terjadi, tinggi viewport berubah dan event resize menyala. ScrollTrigger secara default melakukan refresh saat resize, artinya dia menghitung ulang semua posisi pin di tengah-tengah scroll. Setiap refresh membuat pin snap ke posisi hasil kalkulasi baru. Itulah hitch besar yang sesekali menyela jitter halusnya.

Jadi bukan satu bug, tapi dua: perselisihan sub-piksel yang konstan, ditambah snap besar setiap URL bar bergerak.

Perbaikannya

Yang akhirnya di-ship sederhana dan sedikit menyakitkan ego: di halaman itu, Lenis tidak diinisialisasi sama sekali di mobile. Deteksi perangkat sentuh, dan kalau sentuh, biarkan scroll native yang bekerja. Kuncinya adalah menyadari bahwa scroll sentuhan di HP itu sudah smooth dan fisikanya sudah benar dari sananya; Lenis tidak menambah apa-apa di sana kecuali satu sopir ekstra yang berebut setir. Lenis tetap hidup di desktop, tempat dia memang memberi rasa sinematik itu.

Ditambah satu baris konfigurasi untuk masalah URL bar: ScrollTrigger.config() dengan ignoreMobileResize: true, supaya resize akibat URL bar tidak memicu refresh di tengah scroll.

ScrollTrigger.config({ ignoreMobileResize: true });
 
const isTouchDevice =
  window.matchMedia('(hover: none) and (pointer: coarse)').matches;
 
let lenis = null;
 
if (!isTouchDevice) {
  lenis = new Lenis();
  lenis.on('scroll', ScrollTrigger.update);
  gsap.ticker.add((time) => {
    lenis.raf(time * 1000);
  });
  gsap.ticker.lagSmoothing(0);
}

Hasilnya di HP: pin terkunci rapat, scrub mengikuti jari tanpa getar, URL bar bebas muncul-hilang tanpa membuat apa pun melompat. Desktop tidak berubah sama sekali.

Satu catatan penting: plumbing integrasi di atas, lenis.on('scroll', ScrollTrigger.update) plus menjalankan lenis.raf dari ticker GSAP dan gsap.ticker.lagSmoothing(0), itu wajib kalau kamu memang mempertahankan Lenis dan ScrollTrigger bersamaan. Tanpa itu, semuanya lebih parah. Tapi jangan tertipu: di mobile dengan section yang di-pin, plumbing itu perlu tapi tidak cukup. Dia menyinkronkan dua sopir, dia tidak menghilangkan fakta bahwa sopirnya ada dua.

Pelajaran

Library smooth-scroll dan pinned scrub memperebutkan satu sumber kebenaran yang sama: posisi scroll. Salah satu harus menang. Di desktop, Lenis boleh menang karena wheel event memang kasar dan smoothing-nya terasa mewah. Di mobile, scroll native itu sendiri sudah smooth scroll, jadi biarkan dia menang, dan jangan init Lenis di sana. Checklist saya sekarang untuk setiap halaman dengan pin:

Pertama, ignoreMobileResize: true selalu terpasang kalau ada pin, apa pun library scroll-nya. Kedua, deteksi sentuh dan lewati smooth-scroll library di perangkat sentuh, kecuali ada alasan yang sangat spesifik. Ketiga, dan ini yang paling sering dilupakan: tes pin di HP sungguhan dengan URL bar yang benar-benar naik-turun. Emulasi DevTools tidak menggerakkan URL bar, jadi kelas bug ini tidak akan pernah muncul di laptop kamu. Dia hanya muncul di tangan user, di perangkat yang paling sering dipakai untuk membuka halamanmu.