Setelah menguasai teknik-teknik dasar ScrollTrigger seperti fade-in, parallax, dan pinning, saatnya memasuki wilayah advanced yang benar-benar membedakan website profesional dari yang biasa-biasa saja.
Di level ini, kita tidak hanya menganimasikan elemen HTML biasa, tetapi juga memanipulasi:
- SVG (Scalable Vector Graphics): Gambar vektor yang bisa di-animate per path atau atribut.
- Video: Sinkronisasi playback video dengan posisi scroll.
- Canvas: Rendering grafis real-time yang bisa dikendalikan scroll.
Kombinasi teknologi ini membuka kemungkinan tak terbatas untuk menciptakan pengalaman web yang benar-benar unik dan memorable.
Mengapa Teknik Advanced Ini Penting?
- Diferensiasi Brand: Sedikit website yang menggunakan kombinasi SVG + ScrollTrigger atau Video Scrubbing. Ini membuat brand Anda menonjol.
- Engagement Tinggi: Pengguna akan menghabiskan lebih banyak waktu di halaman karena pengalaman interaktifnya yang menarik.
- Storytelling Kuat: SVG animated dan video scrubbing sempurna untuk menceritakan narrative produk atau brand story yang kompleks.
- Performance Awareness: Menguasai Canvas dan optimization techniques menunjukkan pemahaman mendalam tentang performa web.
Teknik 1: SVG Path Animation dengan ScrollTrigger

Konsep Dasar
SVG memiliki properti strokeDasharray dan strokeDashoffset yang memungkinkan kita "menggambar" garis secara progressive.
Rumus:
textstrokeDasharray: [panjang garis] strokeDashoffset: [offset garis] (0 = terlihat penuh, [panjang] = tersembunyi)
Dengan mengubah strokeDashoffset seiring scroll, kita membuat efek "drawing on scroll".
Kegunaan Real-World:
- Menggambar roadmap produk saat user scroll
- Animasi diagram alur (flowchart)
- Animasi signature atau autograph
- Timeline visual yang di-trigger scroll
Setup SVG yang Benar
<!-- SVG harus memiliki viewBox yang benar agar responsif -->
<svg viewBox="0 0 200 200" width="100%" height="auto">
<!-- Path dengan stroke yang tebal agar terlihat jelas -->
<path id="drawPath"
d="M 20 20 Q 100 20 180 100 T 20 180"
stroke="white"
stroke-width="4"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"/>
</svg>
Properti Penting:
fill="none": Jangan isi path, hanya stroke (garis tepi).stroke-linecap="round": Ujung garis membulat (lebih halus).stroke-linejoin="round": Sudut path membulat (lebih smooth).
Animasi dengan GSAP
svg-path-animation.html
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ScrollTrigger Advanced - SVG Path Animation (Tailwind)</title>
<!-- Tailwind CDN -->
<script src="<https://cdn.tailwindcss.com>"></script>
</head>
<body
class="min-h-screen bg-[radial-gradient(circle_at_top,_#0f0c29,_#302b63,_#24243e)] text-white overflow-x-hidden font-sans"
>
<!-- INTRO -->
<section
class="min-h-screen flex flex-col items-center justify-center text-center px-5"
>
<h1
class="text-[clamp(2.5rem,5vw,4rem)] mb-5 bg-gradient-to-r from-fuchsia-500 to-indigo-700 bg-clip-text text-transparent font-bold"
>
SVG Path Drawing
</h1>
<p class="text-lg opacity-80 mb-6 max-w-xl">
Scroll ke bawah untuk melihat garis SVG “digambar” secara otomatis.
</p>
<div
class="mt-5 text-2xl animate-bounce select-none"
>
↓ Scroll Down ↓
</div>
</section>
<!-- SECTION 1: SQUARE -->
<section class="min-h-screen flex items-center justify-center px-5">
<div
class="w-full max-w-5xl flex flex-wrap gap-10 items-center justify-between"
>
<div class="flex-1 min-w-[260px] flex flex-col items-center">
<svg
viewBox="0 0 200 200"
class="w-full max-w-xs h-auto drop-shadow-[0_0_10px_rgba(255,0,204,0.4)]"
>
<defs>
<linearGradient
id="grad1"
x1="0%"
y1="0%"
x2="100%"
y2="100%"
>
<stop offset="0%" style="stop-color:#ff00cc;stop-opacity:1" />
<stop offset="100%" style="stop-color:#00ffcc;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Path kotak sederhana -->
<path
d="M 20 20 L 180 20 L 180 180 L 20 180 Z"
stroke="url(#grad1)"
stroke-width="6"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
class="draw-me"
/>
</svg>
<div
class="mt-5 text-2xl font-semibold bg-gradient-to-r from-fuchsia-500 to-teal-300 bg-clip-text text-transparent"
>
Square Shape
</div>
</div>
<div class="flex-1 min-w-[260px] px-3">
<h2 class="text-3xl mb-3 text-teal-300">
Teknik Dasar: Garis Lurus
</h2>
<p class="text-[1.05rem] leading-relaxed text-slate-100/80">
Ini adalah bentuk dasar animasi SVG path. Dengan mengubah properti
de>stroke-dashoffset</code> dari panjang total path menjadi 0,
kita menciptakan ilusi garis yang sedang digambar.
</p>
</div>
</div>
</section>
<!-- SPACER -->
<div
class="h-[30vh] flex items-center justify-center opacity-40 italic text-sm"
>
Lanjut ke bentuk organik...
</div>
<!-- SECTION 2: WAVE -->
<section class="min-h-screen flex items-center justify-center px-5">
<div
class="w-full max-w-5xl flex flex-wrap gap-10 items-center justify-between"
>
<div class="flex-1 min-w-[260px] px-3 text-right">
<h2 class="text-3xl mb-3 text-teal-300">
Teknik Organik: Kurva Bezier
</h2>
<p class="text-[1.05rem] leading-relaxed text-slate-100/80">
Path yang lebih kompleks dengan lengkungan (curves) memberikan
kesan yang lebih dinamis dan organik. Sangat cocok untuk
storytelling visual atau ilustrasi flow.
</p>
</div>
<div class="flex-1 min-w-[260px] flex flex-col items-center">
<svg
viewBox="0 0 200 200"
class="w-full max-w-xs h-auto drop-shadow-[0_0_10px_rgba(255,0,204,0.4)]"
>
<defs>
<linearGradient
id="grad2"
x1="0%"
y1="0%"
x2="100%"
y2="0%"
>
<stop offset="0%" style="stop-color:#00ffcc;stop-opacity:1" />
<stop offset="100%" style="stop-color:#ff00cc;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Path bergelombang -->
<path
d="M 10 100 Q 50 10 90 100 T 190 100"
stroke="url(#grad2)"
stroke-width="6"
fill="none"
stroke-linecap="round"
class="draw-me"
/>
</svg>
<div
class="mt-5 text-2xl font-semibold bg-gradient-to-r from-teal-300 to-fuchsia-500 bg-clip-text text-transparent"
>
Wavy Line
</div>
</div>
</div>
</section>
<!-- SPACER -->
<div
class="h-[30vh] flex items-center justify-center opacity-40 italic text-sm"
>
Lanjut ke bentuk kompleks...
</div>
<!-- SECTION 3: STAR -->
<section class="min-h-screen flex items-center justify-center px-5">
<div
class="w-full max-w-5xl flex flex-wrap gap-10 items-center justify-between"
>
<div class="flex-1 min-w-[260px] flex flex-col items-center">
<svg
viewBox="0 0 200 200"
class="w-full max-w-xs h-auto drop-shadow-[0_0_10px_rgba(255,0,204,0.4)]"
>
<defs>
<linearGradient
id="grad3"
x1="50%"
y1="0%"
x2="50%"
y2="100%"
>
<stop offset="0%" style="stop-color:#ff00cc;stop-opacity:1" />
<stop offset="50%" style="stop-color:#ffff00;stop-opacity:1" />
<stop offset="100%" style="stop-color:#ff00cc;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Path bintang kompleks -->
<path
d="M 100 10 L 123 65 L 182 65 L 135 102 L 153 160 L 100 125 L 47 160 L 65 102 L 18 65 L 77 65 Z"
stroke="url(#grad3)"
stroke-width="4"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
class="draw-me"
/>
</svg>
<div
class="mt-5 text-2xl font-semibold bg-gradient-to-r from-fuchsia-500 via-yellow-300 to-fuchsia-500 bg-clip-text text-transparent"
>
Complex Star
</div>
</div>
<div class="flex-1 min-w-[260px] px-3">
<h2 class="text-3xl mb-3 text-teal-300">
Teknik Lanjut: Poligon Tertutup
</h2>
<p class="text-[1.05rem] leading-relaxed text-slate-100/80">
Path tertutup dengan banyak sudut (vertex). ScrollTrigger
menghitung panjang total keliling bentuk ini dan menganimasikannya
dengan presisi tinggi.
</p>
</div>
</div>
</section>
<div
class="h-[50vh] flex items-center justify-center opacity-40 italic text-sm"
>
Selesai! Scroll ke atas untuk mengulang.
</div>
<!-- GSAP + ScrollTrigger -->
<script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
<script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js>"></script>
<script>
gsap.registerPlugin(ScrollTrigger);
function initSvgAnimations() {
const paths = document.querySelectorAll(".draw-me");
paths.forEach((path) => {
const length = path.getTotalLength();
// State awal: garis disembunyikan
path.style.strokeDasharray = length;
path.style.strokeDashoffset = length;
const triggerElement = path.closest("section");
gsap.to(path, {
strokeDashoffset: 0,
ease: "none",
scrollTrigger: {
trigger: triggerElement,
start: "top center",
end: "bottom center",
scrub: 1,
markers: false
}
});
});
}
window.addEventListener("load", initSvgAnimations);
</script>
</body>
</html>
Penjelasan
path.getTotalLength(): Fungsi ini sangat penting. Ia menghitung berapa piksel panjang total dari garis SVG tersebut, tidak peduli seberapa rumit bentuknya (kurva, zig-zag, lingkaran).strokeDasharray= length: Kita membuat pola garis putus-putus di mana panjang "garis" dan panjang "spasi"-nya sama persis dengan panjang total path.strokeDashoffset= length: Kita menggeser pola garis tersebut sejauh panjang totalnya. Akibatnya, yang terlihat di layar hanyalah bagian "spasi" (kosong), sehingga garis tampak tersembunyi.- GSAP
to(..., { strokeDashoffset: 0 }): Saat ScrollTrigger aktif, GSAP mengembalikan offset ke 0 secara bertahap. Ini membuat bagian "garis" masuk kembali ke tampilan, menciptakan ilusi seolah-olah garis tersebut sedang digambar dari awal sampai akhir. scrub: 1: Ini membuat animasi terikat dengan scrollbar. Jika Anda berhenti scroll, animasi berhenti. Jika scroll mundur, animasi mundur (garis menghapus diri sendiri). Angka1memberikan sedikit efek smoothing (inersia) agar gerakan terasa lebih lembut.
Teknik 2: Video Scrubbing dengan ScrollTrigger

Konsep Dasar
Biasanya video dimainkan secara otomatis. Tapi dengan ScrollTrigger, kita bisa:
- Pause & Play video berdasarkan scroll position.
- Scrub (menggeser) frame video mengikuti scroll, seperti timeline editor.
- Timeline Visualization: Video menjadi "progress bar" visual yang menunjukkan narasi sesuai scroll.
Kegunaan Real-World:
- Demo produk yang bisa dikontrol user via scroll.
- Explainer video yang progresif (frame demi frame saat scroll).
- Dokumentasi visual yang interactive.
Contoh Kode
file: video-scrubbing.html
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ScrollTrigger + Video Scrubbing (Tailwind)</title>
<!-- Tailwind CDN -->
<script src="<https://cdn.tailwindcss.com>"></script>
</head>
<body class="bg-slate-950 text-slate-50 overflow-x-hidden">
<!-- Intro -->
<section
class="min-h-screen flex flex-col items-center justify-center text-center px-4 bg-gradient-to-b from-slate-900 to-slate-950"
>
<h1
class="text-4xl md:text-5xl lg:text-6xl font-extrabold tracking-tight bg-gradient-to-r from-sky-400 via-fuchsia-500 to-orange-400 bg-clip-text text-transparent mb-4"
>
Scroll‑Driven Video
</h1>
<p class="max-w-xl text-slate-300 text-base md:text-lg">
Geser halaman untuk mengontrol timeline video secara presisi. Cocok
untuk demo produk sinematik atau storytelling interaktif.
</p>
<div
class="mt-10 text-xs tracking-[0.25em] uppercase text-slate-400 flex flex-col items-center gap-1 animate-bounce"
>
<span class="text-xl">↓</span>
<span>Scroll untuk mulai</span>
</div>
</section>
<!-- Area scroll panjang -->
<section class="h-[450vh] bg-slate-950">
<!-- Video sticky di tengah layar -->
<div
class="sticky top-0 h-screen flex items-center justify-center overflow-hidden"
>
<div
class="relative w-full max-w-5xl aspect-video rounded-3xl overflow-hidden shadow-[0_40px_120px_rgba(15,23,42,0.95)] border border-slate-700/50 bg-slate-900"
>
<!-- VIDEO -->
<video
id="scrubVideo"
playsinline
webkit-playsinline
preload="auto"
muted
class="w-full h-full object-cover brightness-[0.85] contrast-[1.08] saturate-[1.1] scale-[1.02]"
>
<source
src="<http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4>"
type="video/mp4"
/>
Browser Anda tidak mendukung video.
</video>
<!-- Gradient atas & bawah supaya teks kebaca -->
<div
class="pointer-events-none absolute inset-x-0 top-0 h-1/3 bg-gradient-to-b from-slate-900/90 to-transparent z-10"
></div>
<div
class="pointer-events-none absolute inset-x-0 bottom-0 h-2/5 bg-gradient-to-t from-slate-950/95 to-transparent z-10"
></div>
<!-- Badge + pill yang SELALU tampil -->
<div
class="pointer-events-none absolute inset-0 z-20 flex flex-col justify-end gap-3 px-6 md:px-10 pb-7 md:pb-9"
>
<div
class="inline-flex items-center gap-2 text-[0.7rem] tracking-[0.26em] uppercase text-indigo-200"
>
<span
class="w-1.5 h-1.5 rounded-full bg-emerald-400 shadow-[0_0_0_4px_rgba(52,211,153,0.4)]"
></span>
ScrollSync Engine
</div>
<!-- bar / pill di bawah -->
<div
class="mt-3 inline-flex items-center px-3 py-1 rounded-full bg-slate-900/80 border border-slate-600/60 text-[0.75rem] text-slate-100"
>
GSAP ScrollTrigger • Timeline terikat ke posisi scroll
</div>
</div>
<!-- STEP TEKS: hanya SATU yang aktif per fase scroll -->
<!-- STEP 1 -->
<div
id="step-1"
class="pointer-events-none absolute inset-0 z-30 flex flex-col justify-center px-6 md:px-10 opacity-0"
>
<div
class="text-[0.7rem] tracking-[0.22em] uppercase text-indigo-200 mb-2"
>
01 • Discover
</div>
<h3
class="text-2xl md:text-4xl lg:text-[2.8rem] font-semibold mb-2 leading-tight drop-shadow-[0_16px_40px_rgba(15,23,42,0.95)]"
>
Masuk perlahan ke scene kota.
</h3>
<p class="max-w-md text-sm md:text-base text-slate-100/80">
Di awal scroll, pengguna diajak melihat overview scene untuk membangun
konteks visual.
</p>
</div>
<!-- STEP 2 (seperti di screenshot kamu) -->
<div
id="step-2"
class="pointer-events-none absolute inset-0 z-30 flex flex-col justify-center px-6 md:px-10 opacity-0"
>
<div
class="text-[0.7rem] tracking-[0.22em] uppercase text-indigo-200 mb-2"
>
02 • Focus
</div>
<h3
class="text-2xl md:text-4xl lg:text-[2.8rem] font-semibold mb-2 leading-tight drop-shadow-[0_16px_40px_rgba(15,23,42,0.95)]"
>
Detail bergerak mengikuti gesture.
</h3>
<p class="max-w-lg text-sm md:text-base text-slate-100/80">
Saat scroll berlanjut, pengguna bisa mengontrol ritme dan memilih bagian
mana yang ingin diamati lebih lama. Kendalikan setiap frame
<span class="text-sky-400 font-semibold">dengan scroll.</span>
</p>
</div>
<!-- STEP 3 -->
<div
id="step-3"
class="pointer-events-none absolute inset-0 z-30 flex flex-col justify-center px-6 md:px-10 opacity-0"
>
<div
class="text-[0.7rem] tracking-[0.22em] uppercase text-indigo-200 mb-2"
>
03 • Highlight
</div>
<h3
class="text-2xl md:text-4xl lg:text-[2.8rem] font-semibold mb-2 leading-tight drop-shadow-[0_16px_40px_rgba(15,23,42,0.95)]"
>
Ending sinematik yang terkendali.
</h3>
<p class="max-w-md text-sm md:text-base text-slate-100/80">
Di bagian akhir, timeline berhenti di frame hero yang siap digunakan
untuk call‑to‑action atau pesan utama produk.
</p>
</div>
</div>
</div>
</section>
<!-- Outro -->
<section
class="min-h-[60vh] flex items-center justify-center text-center px-4 bg-gradient-to-t from-slate-900 to-slate-950"
>
<div class="max-w-xl">
<h2
class="text-2xl md:text-3xl lg:text-4xl font-bold mb-3 text-slate-50"
>
Plug & play untuk landing page premium.
</h2>
<p class="text-slate-300 text-sm md:text-base">
Ganti saja sumber videonya dengan demo produkmu, sesuaikan teks di tiap
fase, dan teknik ini siap dipakai di project nyata dengan nuansa
sinematik.
</p>
</div>
</section>
<!-- GSAP & ScrollTrigger -->
<script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
<script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js>"></script>
<script>
gsap.registerPlugin(ScrollTrigger);
const video = document.getElementById("scrubVideo");
// Pastikan video siap sehingga duration sudah terbaca
video.addEventListener("loadedmetadata", () => {
initScrollScrub();
});
function initScrollScrub() {
video.pause();
video.currentTime = 0;
// 1) Scrub video: currentTime mengikuti scroll
gsap.to(video, {
currentTime: video.duration || 1,
ease: "none",
scrollTrigger: {
trigger: ".video-section",
start: "top top",
end: "bottom bottom",
scrub: 1
}
});
// 2) Step-teks yang muncul di fase scroll berbeda
const steps = [
{ id: "#step-1", start: "8%", end: "30%" },
{ id: "#step-2", start: "35%", end: "65%" },
{ id: "#step-3", start: "70%", end: "95%" }
];
steps.forEach((step) => {
gsap.fromTo(
step.id,
{ opacity: 0, y: 30 },
{
opacity: 1,
y: 0,
ease: "power2.out",
scrollTrigger: {
trigger: ".video-section",
start: `${step.start} top`,
end: `${step.end} top`,
scrub: true
}
}
);
gsap.to(step.id, {
opacity: 0,
y: -20,
ease: "power2.out",
scrollTrigger: {
trigger: ".video-section",
start: `${step.end} top`,
end: `${parseInt(step.end) + 5}% top`,
scrub: true
}
});
});
}
// Jika browser mencoba autoplay, paksa pause
video.addEventListener("play", () => {
if (!ScrollTrigger.getAll().length) {
video.pause();
}
});
</script>
</body>
</html>
Penjelasan:
currentTimeadalah posisi waktu video (dalam detik).- GSAP membuat animasi dari
currentTime = 0kecurrentTime = video.duration. - ScrollTrigger:
trigger: ".video-section"→ rentang scroll dihitung sepanjang section tinggi 450vh tadi.start: "top top"→ saat bagian atas.video-sectionmenyentuh atas viewport, scrubbing dimulai.end: "bottom bottom"→ saat bagian bawah.video-sectionmencapai bawah viewport, scrubbing selesai (video berada di frame terakhir).scrub: 1→ pergerakan animasi mengikuti scroll dengan sedikit smoothing 1 detik. Scroll naik ↔ video mundur; scroll turun ↔ video maju.
ease: "none"→ kecepatan perubahancurrentTimelinear, agar benar‑benar sinkron dengan pergerakan scroll.
Teknik 3: Canvas Animation dengan ScrollTrigger

Konsep Dasar
Canvas adalah area pixel-by-pixel yang bisa di-render dengan JavaScript. Ini sempurna untuk:
- Particle Systems: Ribuan partikel yang bergerak smooth.
- Real-time Graphics: Visualisasi data, waveform, atau animasi komplex.
- Interactive Visualizations: Grafik yang merespons scroll atau mouse.
Keunggulan Canvas:
- Performa tinggi bahkan untuk ratusan elemen (lebih cepat dari SVG untuk jumlah besar).
- Kontrol pixel-level penuh.
- Bisa merender 3D dengan WebGL.
Kelemahan Canvas:
- Tidak semudah SVG untuk markup/CSS.
- Perlu JavaScript untuk setiap frame.
- Text rendering tidak sebaik SVG.
Contoh Kode
file: canvas-scrolltrigger.html
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ScrollTrigger + Canvas (Tailwind)</title>
<script src="<https://cdn.tailwindcss.com>"></script>
</head>
<body class="min-h-screen bg-slate-950 text-slate-50 overflow-x-hidden font-sans">
<!-- INTRO -->
<section class="min-h-screen flex flex-col items-center justify-center text-center px-5 bg-gradient-to-b from-slate-900 to-slate-950">
<h1 class="text-[clamp(2.4rem,5vw,3.4rem)] font-extrabold tracking-tight bg-gradient-to-r from-cyan-400 via-sky-500 to-fuchsia-500 bg-clip-text text-transparent mb-4">
Canvas Energy Bar
</h1>
<p class="max-w-xl text-base md:text-lg text-slate-300">
Scroll ke bawah untuk mengisi “energy bar” di dalam canvas. Nilai progress (0–100%) dikendalikan penuh oleh posisi scroll Anda.
</p>
<div class="mt-8 text-xs tracking-[0.25em] uppercase text-slate-400 flex flex-col items-center gap-1 animate-bounce">
<span class="text-xl">↓</span>
<span>Scroll untuk mulai</span>
</div>
</section>
<!-- MAIN CANVAS SECTION -->
<section class="canvas-section min-h-[250vh] flex items-center justify-center px-5 bg-slate-950 relative">
<!-- Sticky Wrapper -->
<div class="sticky top-0 w-full max-w-5xl flex flex-col md:flex-row items-center gap-10 min-h-screen">
<!-- 1. Card Canvas -->
<div class="flex-1 flex items-center justify-center w-full">
<div class="relative w-full max-w-xl aspect-[16/7] bg-slate-900/80 border border-slate-700/70 rounded-3xl shadow-[0_30px_80px_rgba(15,23,42,0.9)] flex items-center justify-center">
<canvas id="energyCanvas" class="w-[90%] h-[55%]"></canvas>
<div class="absolute inset-x-0 bottom-4 flex items-center justify-between px-8 text-xs md:text-sm text-slate-300/80">
<span>Scroll Progress → Energy</span>
<span id="energyLabel" class="font-semibold text-cyan-300">0%</span>
</div>
</div>
</div>
<!-- 2. Teks Penjelasan -->
<div class="flex-1 max-w-md space-y-4">
<p class="text-sm font-mono text-cyan-300/80">Teknik 3 · Canvas + ScrollTrigger</p>
<h2 class="text-2xl md:text-3xl font-semibold text-cyan-300">
Menghubungkan progress scroll ke gambar di canvas.
</h2>
<p class="text-slate-200/85 text-sm md:text-base leading-relaxed">
Alih-alih memanipulasi elemen DOM yang berat, kita menggunakan Canvas API untuk merender bar energi secara real-time.
</p>
<p class="text-slate-200/85 text-sm md:text-base leading-relaxed">
ScrollTrigger mengirimkan nilai 0 sampai 1 berdasarkan posisi scroll. Kita menggunakan nilai ini untuk menggambar ulang (redraw) canvas setiap frame, menciptakan animasi yang super-smooth dan sinkron.
</p>
</div>
</div>
</section>
<!-- OUTRO -->
<section class="h-[40vh] flex items-center justify-center text-slate-500">
Akhir demo canvas. Scroll naik–turun untuk melihat energinya berubah.
</section>
<!-- GSAP -->
<script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
<script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js>"></script>
<script>
gsap.registerPlugin(ScrollTrigger);
const canvas = document.getElementById("energyCanvas");
const ctx = canvas.getContext("2d");
const label = document.getElementById("energyLabel");
// Object proxy untuk menyimpan nilai progress animasi
let progressProxy = { value: 0 };
// 1. Fungsi Resize: Menjaga ketajaman canvas di semua layar
function resizeCanvas() {
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
// Set dimensi fisik canvas (dikali DPR untuk retina display)
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
// Normalisasi sistem koordinat agar coding drawing lebih mudah (0..width)
ctx.scale(dpr, dpr);
// Simpan dimensi logis untuk dipakai di fungsi draw
canvas.logicalWidth = rect.width;
canvas.logicalHeight = rect.height;
// Redraw saat resize
draw(progressProxy.value);
}
// 2. Fungsi Draw: Menggambar ulang canvas setiap frame
function draw(progress) {
const w = canvas.logicalWidth;
const h = canvas.logicalHeight;
// Bersihkan canvas sebelum menggambar frame baru
ctx.clearRect(0, 0, w, h);
const radius = h / 2;
// A. Gambar Track (Background Bar)
ctx.fillStyle = "rgba(15, 23, 42, 0.6)";
ctx.strokeStyle = "rgba(51, 65, 85, 0.8)";
ctx.lineWidth = 2;
ctx.beginPath();
if (ctx.roundRect) {
ctx.roundRect(0, 0, w, h, radius);
} else {
ctx.rect(0, 0, w, h);
}
ctx.fill();
ctx.stroke();
// B. Gambar Fill (Isi Bar)
const fillWidth = Math.max(0, w * progress);
if (fillWidth > 0) {
ctx.save();
// Clip area agar fill tidak keluar dari rounded corner
ctx.beginPath();
if (ctx.roundRect) {
ctx.roundRect(0, 0, w, h, radius);
} else {
ctx.rect(0, 0, w, h);
}
ctx.clip();
// Gradient Warna
const grad = ctx.createLinearGradient(0, 0, w, 0);
grad.addColorStop(0, "#22c55e"); // Hijau
grad.addColorStop(0.5, "#0ea5e9"); // Biru Langit
grad.addColorStop(1, "#d946ef"); // Ungu
ctx.fillStyle = grad;
ctx.fillRect(0, 0, fillWidth, h);
ctx.restore();
}
// C. Gambar Garis Pembagi (Ticks)
ctx.strokeStyle = "rgba(255, 255, 255, 0.1)";
ctx.lineWidth = 2;
const steps = 5;
for (let i = 1; i < steps; i++) {
const x = (w / steps) * i;
ctx.beginPath();
ctx.moveTo(x, h * 0.2);
ctx.lineTo(x, h * 0.8);
ctx.stroke();
}
// D. Update Label Teks
if(label) label.textContent = Math.round(progress * 100) + "%";
}
// Inisialisasi
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
draw(0); // Gambar frame awal
// 3. Animasi GSAP + ScrollTrigger
gsap.to(progressProxy, {
value: 1,
ease: "none",
scrollTrigger: {
trigger: ".canvas-section",
start: "top center", // Mulai saat bagian atas section di tengah layar
end: "bottom center", // Selesai saat bagian bawah section di tengah layar
scrub: 1, // Smooth scrubbing 1 detik
},
onUpdate: () => {
// Trik: Paksa nilai jadi 1 jika sudah > 99% agar tidak "nyangkut" di 99%
if (progressProxy.value > 0.91) progressProxy.value = 1;
draw(progressProxy.value);
}
});
</script>
</body>
</html>
Penjelasan
- Canvas vs DOM: Kita tidak menggunakan
<div>CSS width untuk animasi ini, melainkan elemen<c**anvas>**. Ini memberikan performa lebih tinggi (60fps stabil) karena browser hanya menggambar satu elemen bitmap, bukan me-layout ulang elemen HTML. - Proxy Object: GSAP menganimasikan properti
valuepada sebuah objek JavaScript biasa (progressProxy) dari 0 ke 1. Objek ini tidak terlihat di layar, tapi nilainya kita pakai. - onUpdate: Setiap kali scroll bergeser, GSAP mengupdate nilai proxy tadi. Fungsi
onUpdatemengambil nilai baru tersebut, lalu memerintahkan Canvas untuk menggambar ulang (draw) bar energi dengan lebar baru. - Math Logic: Logika
progressProxy.value > 0.91ditambahkan untuk memastikan bar benar-benar penuh (100%) saat scroll selesai, mengatasi masalah rounding error di mana animasi kadang berhenti di 96%.
Teknik 4: Gabungan Advanced (SVG + Video + Data)

<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Teknik 4: Immersive Timeline</title>
<script src="<https://cdn.tailwindcss.com>"></script>
</head>
<body class="bg-slate-950 text-slate-50 font-sans overflow-x-hidden selection:bg-cyan-500/30">
<!-- BACKGROUND VIDEO LAYER -->
<div class="fixed inset-0 w-full h-full z-0">
<video
id="bgVideo"
class="w-full h-full object-cover opacity-40 grayscale-[50%] brightness-75"
playsinline webkit-playsinline muted preload="auto">
<source src="<http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4>" type="video/mp4">
</video>
<div class="absolute inset-0 bg-gradient-to-r from-slate-950 via-slate-950/80 to-slate-950/60"></div>
<div class="absolute inset-0 bg-gradient-to-t from-slate-950 via-transparent to-slate-950/40"></div>
</div>
<!-- CONTENT LAYER -->
<div class="relative z-10">
<!-- HERO -->
<section class="h-screen flex flex-col justify-center px-8 md:px-20 max-w-4xl">
<h1 class="text-6xl md:text-8xl font-bold tracking-tight leading-tight mb-6">
PROJECT <br>
<span class="text-transparent bg-clip-text bg-gradient-to-r from-cyan-400 to-teal-400">AERO</span>
</h1>
<p class="text-xl text-slate-400 max-w-xl border-l-4 border-cyan-500/30 pl-6">
Sebuah eksplorasi visual menggabungkan data telemetri, video scrubbing, dan navigasi SVG dalam satu pengalaman timeline yang mulus.
</p>
<div class="mt-12 animate-pulse text-sm tracking-[0.3em] text-cyan-500 font-mono">
SCROLL TO BEGIN_
</div>
</section>
<!-- TIMELINE SECTION -->
<section class="timeline-section h-[400vh] relative">
<div class="sticky top-0 h-screen w-full flex items-center pl-8 md:pl-20 pr-8">
<!-- SVG TIMELINE -->
<div class="relative h-[60vh] w-[60px] flex-shrink-0 mr-10 md:mr-20 hidden md:block">
<svg class="h-full w-full overflow-visible" viewBox="0 0 60 600" preserveAspectRatio="none">
<path d="M 30 0 L 30 600" stroke="#334155" stroke-width="2" fill="none" />
<path id="activeLine" d="M 30 0 L 30 600" stroke="#22d3ee" stroke-width="4" stroke-linecap="round" fill="none" />
ircle cx="30" cy="0" r="6" class="fill-slate-950 stroke-slate-600 stroke-2" />
ircle cx="30" cy="200" r="6" class="dot-marker fill-slate-950 stroke-slate-600 stroke-2" />
ircle cx="30" cy="400" r="6" class="dot-marker fill-slate-950 stroke-slate-600 stroke-2" />
ircle cx="30" cy="600" r="6" class="dot-marker fill-slate-950 stroke-slate-600 stroke-2" />
</svg>
</div>
<!-- DYNAMIC CONTENT AREA -->
<div class="flex-1 relative h-[60vh] flex items-center">
<!-- STEP 1 -->
<div class="step-content absolute top-1/2 -translate-y-1/2 opacity-0 translate-x-10 transition-all duration-700 w-full max-w-2xl">
<div class="font-mono text-cyan-400 text-sm mb-2 tracking-widest">PHASE 01</div>
<h2 class="text-5xl md:text-6xl font-bold text-white mb-6">System Initiation</h2>
<p class="text-lg text-slate-300 leading-relaxed border-l-2 border-slate-700 pl-6">
Sistem melakukan boot-up sequence. Video scrubbing disinkronisasi dengan posisi scroll pengguna untuk memberikan feedback visual instan.
</p>
<div class="mt-8 grid grid-cols-2 gap-4 font-mono text-xs opacity-70">
<div class="bg-slate-900/80 p-3 rounded border border-slate-700">
<div>CPU_LOAD</div>
<div class="text-xl text-green-400 mt-1">12%</div>
</div>
<div class="bg-slate-900/80 p-3 rounded border border-slate-700">
<div>MEMORY</div>
<div class="text-xl text-green-400 mt-1">OK</div>
</div>
</div>
</div>
<!-- STEP 2 -->
<div class="step-content absolute top-1/2 -translate-y-1/2 opacity-0 translate-x-10 transition-all duration-700 w-full max-w-2xl">
<div class="font-mono text-purple-400 text-sm mb-2 tracking-widest">PHASE 02</div>
<h2 class="text-5xl md:text-6xl font-bold text-white mb-6">Vertical Liftoff</h2>
<p class="text-lg text-slate-300 leading-relaxed border-l-2 border-slate-700 pl-6">
Mencapai ketinggian jelajah. Perhatikan bagaimana garis timeline SVG terisi seiring dengan pergerakan video drone yang mulai terbang.
</p>
<div class="mt-8 flex items-center gap-4">
<div class="h-2 flex-1 bg-slate-800 rounded-full overflow-hidden">
<div class="h-full bg-purple-500 w-[75%] animate-pulse"></div>
</div>
<span class="font-mono text-purple-400">THRUST 75%</span>
</div>
</div>
<!-- STEP 3 -->
<div class="step-content absolute top-1/2 -translate-y-1/2 opacity-0 translate-x-10 transition-all duration-700 w-full max-w-2xl">
<div class="font-mono text-orange-400 text-sm mb-2 tracking-widest">PHASE 03</div>
<h2 class="text-5xl md:text-6xl font-bold text-white mb-6">Stabilization</h2>
<p class="text-lg text-slate-300 leading-relaxed border-l-2 border-slate-700 pl-6">
Gyroscope aktif. Kamera stabil. Data telemetri menunjukkan kondisi optimal untuk pengambilan gambar resolusi tinggi.
</p>
</div>
<!-- STEP 4 -->
<div class="step-content absolute top-1/2 -translate-y-1/2 opacity-0 translate-x-10 transition-all duration-700 w-full max-w-2xl">
<div class="font-mono text-emerald-400 text-sm mb-2 tracking-widest">PHASE 04</div>
<h2 class="text-5xl md:text-6xl font-bold text-white mb-6">Mission Complete</h2>
<p class="text-lg text-slate-300 leading-relaxed border-l-2 border-slate-700 pl-6">
Timeline selesai. Seluruh data telah direkam. Siap untuk proses download log penerbangan.
</p>
<button class="mt-8 group flex items-center gap-3 px-8 py-4 bg-emerald-500/10 border border-emerald-500/50 hover:bg-emerald-500 hover:text-black transition-all rounded-none">
<span>DOWNLOAD LOGS</span>
<span class="group-hover:translate-x-1 transition-transform">→</span>
</button>
</div>
</div>
</div>
</section>
<!-- OUTRO -->
<section class="h-[50vh] flex flex-col items-center justify-center relative z-20 bg-slate-950">
<div class="h-[1px] w-20 bg-slate-700 mb-8"></div>
<p class="text-slate-500">End of Transmission</p>
</section>
</div>
<!-- SCRIPTS -->
<script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
<script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js>"></script>
<script>
gsap.registerPlugin(ScrollTrigger);
const video = document.getElementById('bgVideo');
const activeLine = document.getElementById('activeLine');
const steps = document.querySelectorAll('.step-content');
const dots = document.querySelectorAll('.dot-marker');
const length = activeLine.getTotalLength();
activeLine.style.strokeDasharray = length;
activeLine.style.strokeDashoffset = length;
const tl = gsap.timeline({
scrollTrigger: {
trigger: ".timeline-section",
start: "top top",
end: "bottom bottom",
scrub: 1,
}
});
video.pause();
tl.to(video, {
currentTime: video.duration || 10,
ease: "none",
duration: 10
}, 0);
tl.to(activeLine, {
strokeDashoffset: 0,
ease: "none",
duration: 10
}, 0);
const stepDuration = 10 / steps.length;
steps.forEach((step, i) => {
const startTime = i * stepDuration;
tl.fromTo(step,
{ opacity: 0, x: 50, display: 'none' },
{ opacity: 1, x: 0, display: 'block', duration: 0.5,
onStart: () => {
if(dots[i]) gsap.to(dots[i], { stroke: '#22d3ee', strokeWidth: 4, duration: 0.3 });
},
onReverseComplete: () => {
if(dots[i]) gsap.to(dots[i], { stroke: '#475569', strokeWidth: 2, duration: 0.3 });
}
},
startTime + 0.5
);
if (i < steps.length - 1) {
tl.to(step, {
opacity: 0,
y: -30,
duration: 0.5,
display: 'none'
}, startTime + stepDuration - 0.5);
}
});
video.addEventListener('loadedmetadata', () => ScrollTrigger.refresh());
</script>
</body>
</html>
- Video sebagai "Canvas" Latar (Fixed Background)
- Alih-alih video ditaruh di kotak kecil, video dijadikan background penuh layar yang diam (
fixed). - Diberi filter gelap (overlay) agar teks di atasnya selalu terbaca jelas, menciptakan suasana sinematik.
- Alih-alih video ditaruh di kotak kecil, video dijadikan background penuh layar yang diam (
- Satu Garis Waktu Utama (
Master Timeline)- Kita menggunakan GSAP Timeline tunggal. Ini ibarat "benang merah" yang mengikat semua elemen.
- Saat Anda scroll dari 0% ke 100% halaman, timeline ini bergerak dari detik 0 ke detik 10.
- Sinkronisasi Total: Posisi video, panjang garis SVG, dan munculnya teks semua terikat pada satu timeline ini. Tidak ada animasi yang jalan sendiri-sendiri.
- Navigasi Visual (SVG Line)
- Garis vertikal di kiri berfungsi sebagai penunjuk "kita ada di mana".
- Efek garis yang "mengisi" diri sendiri (
strokeDashoffset) memberikan kepuasan visual progress.
- Konten yang "Bernapas" (Layout Asimetris)
- Layout dibuat lega (banyak ruang kosong) dengan teks di kanan dan garis di kiri.
- Teks tidak muncul kaget, tapi masuk perlahan (slide in) memberikan kesan elegan dan premium.
Best Practices untuk ScrollTrigger Advanced
1. Performance Optimization
Problem: Terlalu banyak ScrollTrigger bisa membuat scroll jadi lag di mobile.
Solusi:
- Gunakan
trigger: document.bodydan kalkulasi posisi manual daripada buat trigger per elemen. - Debounce atau throttle resize events.
- Batasi jumlah Canvas elements aktif.
- Gunakan
will-changeCSS untuk optimasi GPU.
css.animated-element {
will-change: transform;
}
2. Accessibility
Problem: Animasi bisa mengganggu user dengan sensitivitas motion.
Solusi: Deteksi dan hormati prefers-reduced-motion.
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (!prefersReducedMotion) {
*// Jalankan animasi normal*
} else {
*// Versi static atau simplified*
}
3. Responsive Design
Canvas dan SVG perlu di-resize saat window berubah. Gunakan ScrollTrigger.refresh().
javascriptwindow.addEventListener('resize', () => {
*// Resize canvas*
canvas.width = canvas.offsetWidth;
*// Refresh ScrollTrigger*
ScrollTrigger.getAll().forEach(trigger => trigger.refresh());
});
4. Mobile Considerations
- Video di mobile sering di-pause untuk hemat data. Berikan fallback (gambar static atau GIF).
- Canvas rendering bisa lambat di mobile. Test di real device, bukan simulasi.
- Kurangi kompleksitas animasi untuk device lebih lemah.
Tools & Resources
- Optimasi SVG: Use SVG Optimize tool untuk mengurangi file size.
- Kompresi Video: Use ffmpeg atau HandBrake untuk kompresi optimal.
- Canvas Debugging: Chrome DevTools -> Sources -> Canvas inspector.
- Performance Monitoring: Use Lighthouse atau WebPageTest.
ScrollTrigger Advanced membuka pintu untuk menciptakan pengalaman web yang truly interactive dan memorable. SVG animasi, video scrubbing, dan canvas graphics bukan hanya untuk portofolio designer, tetapi juga untuk aplikasi bisnis modern yang ingin stand out.
Kunci sukses:
- Mulai dengan 1 teknik: Master SVG dulu, baru kombinasikan dengan video atau canvas.
- Test performa: Jangan sacrifice user experience untuk efek visual.
- Responsive-first: Desain untuk mobile dulu, baru scale ke desktop.
- Dokumentasi & cleanup: Biarkan comment di code agar mudah di-maintain.
Happy animating! 🚀