SVG (Scalable Vector Graphics) adalah gambar yang ditulis sebagai elemen di dalam HTML: "garis dari sini ke sana", "lingkaran di titik ini". Bayangin file potong mesin cutting stiker: isinya cuma petunjuk jalur pisau, jadi bisa dipotong sekecil label atau sebesar spanduk tanpa pecah. Di lesson ini logo Raka (garis kaos, biji kopi di dada, tulisan RAKA) kita buat dengan SVG, lalu kita animasikan: garisnya menggambar dirinya sendiri, bijinya muncul, tulisannya memudar masuk.
🧪 Tebakan cepat: garis kaos di logo panjangnya 358,9. Kalau
stroke-dasharraydiisi tebakan 300, lalustroke-dashoffsetdianimasikan dari 300 ke 0, apa yang terlihat di akhir: garis utuh, atau ada bagian yang gak pernah tergambar? Tebak dulu, jawabannya ada di bagian "Garis yang menggambar dirinya sendiri".
SVG, viewBox, dan path
<svg viewBox="0 0 120 140" width="240" height="280" role="img" aria-label="Logo Raka: kaos dengan biji kopi">
<rect width="120" height="140" rx="12" fill="#1c1917" />
<path class="kaos" d="M45 18 L25 24 L8 42 L20 54 L30 46 L30 102 L90 102 L90 46 L100 54 L112 42 L95 24 L75 18 L60 28 Z"
fill="none" stroke="#f5e6c8" stroke-width="4" stroke-linejoin="round" />
<g class="biji">
<ellipse cx="76" cy="62" rx="7" ry="10" fill="#c47a3d" />
<path d="M76 54 Q72 62 76 70" fill="none" stroke="#1c1917" stroke-width="2" />
</g>
<text class="teks" x="60" y="128" text-anchor="middle" font-size="18" font-weight="bold" fill="#f5e6c8">RAKA</text>
</svg>
viewBox="0 0 120 140"adalah sistem koordinat di dalam gambar: x 0 sampai 120, y 0 sampai 140, (0, 0) di kiri atas seperti canvas.width="240"menampilkannya dua kali lipat: 1 satuan = 2 px, dan tetap tajam di ukuran apa pun karena browser menggambar ulang garisnya, bukan menarik pixel.- Atribut
ddi<path>adalah daftar perintah:M x ypindah pena (moveTo),L x ygaris ke titik itu (lineTo),H xdanV ygaris datar dan tegak,QdanCkurva,Ztutup ke titik awal (closePath). Huruf besar memakai koordinat mutlak, huruf kecil relatif dari posisi pena. - Tiap bentuk adalah elemen di DOM: bisa diberi
class, di-style dengan CSS, dan diberiaddEventListenersendiri.
Animasi dengan CSS, dan jebakan titik putar
Biji kopi muncul dengan membesar dari 0 sambil berputar:
.biji {
transform-box: fill-box; /* kotak acuan = kotak biji itu sendiri */
transform-origin: center;
animation: muncul 0.5s 2s ease-out both;
}
@keyframes muncul {
from { transform: scale(0) rotate(-90deg); }
}
Di elemen SVG, transform-origin bawaannya 0 0, yaitu pojok kiri atas viewBox, dan transform-box bawaannya view-box. Jadi transform-origin: center saja berarti tengah seluruh logo (60, 70), bukan tengah biji. Diukur saat biji setengah jalan, scale(0.5) rotate(-45deg) (Chrome, Firefox dan Safari sama):
CSS di .biji |
Titik putar | Pusat biji saat setengah jalan |
|---|---|---|
tanpa transform-origin |
(0, 0), pojok kiri atas | sekitar (49, -5): di luar logo |
transform-origin: center |
(60, 70), tengah logo | sekitar (63, 62): melayang dari tengah |
transform-box: fill-box + center |
(76, 62), tengah biji | (76, 62): diam di tempat |
both di animation menahan keadaan from (ukuran 0) selama jeda 2 detik, jadi biji gak terlihat sebelum waktunya.
Garis yang menggambar dirinya sendiri
stroke-dasharray membuat garis jadi putus-putus. Satu angka berarti dash sepanjang itu, lalu celah sepanjang itu juga. stroke-dashoffset menggeser pola itu sepanjang garis. Triknya: dash dan celah dibuat sepanjang garis. Offset sepanjang garis berarti yang menutupi garis adalah celahnya (tak terlihat); offset 0 berarti dash-nya (utuh).
Panjangnya diukur dengan JavaScript:
const kaos = document.querySelector('.kaos');
console.log(kaos.getTotalLength()); // 358.8880615234375
Chrome, Firefox dan Safari memberi angka yang sama. Bulatkan ke atas (Math.ceil) jadi 359:
.kaos {
stroke-dasharray: 359;
stroke-dashoffset: 0; /* keadaan dasar: garis utuh */
animation: gambar 2s ease-out both;
}
@keyframes gambar {
from { stroke-dashoffset: 359; } /* awal: tersembunyi */
}
.teks { animation: pudar 0.6s 2.3s both; }
@keyframes pudar { from { opacity: 0; } }
Offset di tengah jalan = panjang × (1 - persen / 100):
| Progres | stroke-dashoffset |
Yang terlihat |
|---|---|---|
| 0% | 359 | belum ada |
| 25% | 269,25 | 89,75 satuan pertama |
| 50% | 179,5 | separuh garis |
| 100% | 0 | garis utuh |
Dengan tebakan 300, dash-nya 300 dan celahnya 300. Di akhir, satuan 0 sampai 300 tertutup dash, tapi 300 sampai 358,9 jatuh di celah: 58,9 satuan terakhir (kerah V dan bahu kanan) gak pernah tergambar. Itu jawaban tebakannya. Lebih aneh lagi, di awal animasi potongan yang sama malah sudah terlihat. Alternatifnya, pathLength="100" di <path> membuat garis dihitung sepanjang 100, jadi stroke-dasharray: 100 selalu pas.
SMIL: animasi di dalam SVG
SVG punya elemen animasi sendiri, ditaruh di dalam elemen yang dianimasikan:
<path class="kaos" d="..." stroke-dasharray="359" stroke-dashoffset="359">
<animate attributeName="stroke-dashoffset" from="359" to="0" dur="2s" fill="freeze" />
</path>
attributeNameadalah atribut yang diubah,from/tonilainya,durdurasinya, danrepeatCount="indefinite"membuatnya berulang terus.fill="freeze"di sini artinya tahan nilai akhir (sepertiforwardsdi CSS), bukan warna isi.- SMIL jalan di Chrome, Firefox dan Safari versi sekarang. Chrome sempat berencana menghapusnya di 2015, lalu membatalkannya.
CSS tetap biasanya lebih dipilih: ilmunya sama dengan animasi CSS lain, @media (prefers-reduced-motion: reduce) bisa mematikannya, dan JavaScript bisa mengendalikannya lewat element.getAnimations() (jeda, putar, lompat ke detik tertentu). Media query CSS gak menyentuh SMIL; untuk menghentikannya kamu butuh svg.pauseAnimations().
@media (prefers-reduced-motion: reduce) {
.kaos, .biji, .teks { animation: none; } /* langsung keadaan akhir */
}
Ini jalan karena keadaan dasarnya adalah keadaan akhir (offset 0, ukuran biasa, opacity 1); keadaan awal cuma ada di from.
SVG atau canvas?
| SVG | Canvas | |
|---|---|---|
| Isinya | elemen di DOM | pixel |
| Diperbesar | tetap tajam | harus digambar ulang dengan DPR |
| Klik di satu bentuk | addEventListener di elemennya |
hit testing sendiri |
| Banyak benda | puluhan sampai ratusan | ribuan partikel tiap frame |
| Cocok untuk | logo, ikon, ilustrasi, chart | partikel, game, efek per pixel |
Logo Raka cocok jadi SVG; uap dengan puluhan partikel per detik cocok di canvas.
⚠️ Jebakan yang sering ketemu
transform-origin: centertanpatransform-box: fill-box. Di SVG, center berarti tengah viewBox, jadi biji melayang dari tengah logo.stroke-dasharraylebih pendek dari garisnya. Ujung garis gak pernah tergambar. Ukur dengangetTotalLength()dan bulatkan ke atas.- Keadaan awal ditaruh di gaya dasar. Kalau
.kaosdasarnyastroke-dashoffset: 359, mematikan animasi untuk reduced motion membuat logonya hilang. Taruh keadaan awal difrom, dan pakaiboth. - Menghitung progres terbalik. Offset 0 artinya selesai, bukan belum mulai: offset = panjang × (1 - persen / 100).
TL;DR: SVG adalah gambar vektor sebagai elemen DOM: viewBox menentukan koordinatnya, d di path berisi perintah M, L, H, V, Q, C, Z. Animasikan dengan CSS: untuk scale atau rotate di elemen SVG, pasang transform-box: fill-box plus transform-origin: center, karena bawaannya pojok viewBox. Efek menggambar garis: stroke-dasharray dan stroke-dashoffset sepanjang garis (diukur getTotalLength(), logo Raka 358,9 jadi 359), lalu animasikan offset dari panjang ke 0; di tengah jalan offset = panjang × (1 - persen / 100). SMIL (<animate>, repeatCount, fill="freeze") jalan di semua browser sekarang, tapi CSS lebih mudah dikendalikan dan dimatikan untuk prefers-reduced-motion. SVG untuk logo dan ikon, canvas untuk ribuan partikel.