Kenapa ini penting
Pengguna menilai kualitas sebuah aplikasi dari hal-hal kecil: tombol yang terasa empuk saat ditekan, kartu yang sedikit terangkat saat disentuh, notifikasi yang meluncur masuk alih-alih muncul tiba-tiba. Gerakan yang halus membantu otak memahami perubahan: dari mana sesuatu datang dan ke mana ia pergi. Di modul ini kamu akan belajar alat visual tingkat lanjut CSS (transition, transform, animation, shadow, gradient, filter) sekaligus CSS custom properties yang membuat semua nilai desain bisa diatur dari satu tempat, termasuk untuk dark mode.
Konsep inti
Transition
Transition membuat perubahan nilai properti berlangsung bertahap. Tanpa transition, :hover langsung meloncat. Dengan transition, perubahan bergerak mulus.
.tombol { transition: background-color 200ms ease, transform 200ms ease; }
Bagiannya: properti apa, durasi, fungsi waktu (ease, ease-in, ease-out, linear, atau cubic-bezier(...)), dan opsional delay. Untuk interaksi UI, durasi 150 sampai 300ms terasa responsif. Hindari transition: all karena ikut menganimasikan properti yang tidak kamu sadari dan bisa boros performa.
Transform
Transform mengubah tampilan elemen tanpa mengubah tata letak di sekitarnya (elemen lain tidak ikut bergeser):
translate(x, y)menggeser, misalnyatranslateY(-4px)untuk efek terangkat.scale(1.05)memperbesar.rotate(45deg)memutar.skew()memiringkan.transform-originmenentukan titik poros.
CSS modern juga punya properti individual translate, scale, dan rotate yang bisa ditulis terpisah, misalnya scale: 1.05;.
Tips performa: properti transform dan opacity bisa dianimasikan browser dengan murah karena tidak memicu perhitungan ulang layout. Menganimasikan width, height, top, atau margin memaksa browser menghitung ulang posisi elemen lain di setiap frame dan bisa membuat animasi patah-patah di HP.
Animation dan @keyframes
Kalau transition butuh pemicu (misalnya hover), animation bisa berjalan sendiri dan punya banyak tahap.
@keyframes denyut {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
.badge { animation: denyut 1.5s ease-in-out infinite; }
Properti animasi: animation-name, animation-duration, animation-timing-function, animation-delay, animation-iteration-count (angka atau infinite), animation-direction (alternate untuk bolak-balik), dan animation-fill-mode (forwards menahan keadaan akhir, both juga menerapkan keadaan awal selama delay).
Aksesibilitas gerak: sebagian orang mengalami pusing atau mual karena animasi. Hormati pengaturan sistem mereka:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Shadow
box-shadow: offsetX offsetY blur spread warna;misalnya0 10px 25px -5px rgb(0 0 0 / 0.15). Bisa ditumpuk dengan koma untuk bayangan yang lebih realistis. Kata kunciinsetmembuat bayangan di dalam.text-shadow: 0 2px 4px rgb(0 0 0 / 0.4);untuk teks.- Bayangan yang halus biasanya memakai warna hitam dengan transparansi rendah, bukan abu-abu pekat.
Gradient
Gradient adalah gambar yang dibuat browser, jadi dipasang lewat background atau background-image:
linear-gradient(135deg, #6366f1, #ec4899)gradasi garis lurus.radial-gradient(circle at top, #fde68a, transparent 60%)gradasi melingkar.conic-gradient(...)gradasi memutar, cocok untuk diagram pie sederhana.- Titik warna (color stop) bisa diberi posisi:
linear-gradient(90deg, red 0 50%, blue 50% 100%)menghasilkan dua blok tegas.
Trik teks bergradasi: background: linear-gradient(...); background-clip: text; color: transparent;.
CSS custom properties (variabel CSS)
Custom property adalah variabel yang hidup di browser. Didefinisikan dengan awalan dua strip dan dibaca dengan var():
:root {
--warna-utama: hsl(221 83% 53%);
--radius: 12px;
}
.tombol { background: var(--warna-utama); border-radius: var(--radius); }
Kelebihannya dibanding variabel Sass (yang akan kamu pelajari nanti):
- Bisa berubah saat runtime: diganti lewat media query, class, atau JavaScript tanpa compile ulang.
- Mengikuti cascade dan inheritance: nilai di
:rootberlaku global, tetapi bisa ditimpa di dalam komponen tertentu. var(--nama, nilai-cadangan)memberi nilai default kalau variabel belum didefinisikan.
Inilah dasar dark mode modern: definisikan warna sebagai variabel, lalu timpa nilainya di dalam @media (prefers-color-scheme: dark) atau pada atribut [data-theme="dark"].
Filter
filter: blur(4px),grayscale(1),brightness(1.2),contrast(1.1),saturate(1.5),drop-shadow(...).drop-shadowberbeda denganbox-shadow: ia mengikuti bentuk asli gambar PNG atau SVG transparan, bukan kotak persegi.backdrop-filter: blur(12px)memburamkan apa yang ada di belakang elemen, dasar efek "glassmorphism" seperti di iOS.
Contoh kode
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Kartu Interaktif</title>
<style>
:root {
--bg: hsl(220 30% 97%);
--surface: white;
--teks: hsl(222 47% 11%);
--utama: hsl(245 75% 60%);
--aksen: hsl(330 80% 60%);
--radius: 16px;
--bayangan: 0 1px 2px rgb(0 0 0 / 0.06), 0 8px 24px -8px rgb(0 0 0 / 0.15);
--durasi: 220ms;
}
/* Dark mode: cukup timpa variabelnya */
@media (prefers-color-scheme: dark) {
:root {
--bg: hsl(222 40% 8%);
--surface: hsl(222 30% 14%);
--teks: hsl(210 40% 96%);
--bayangan: 0 8px 24px -8px rgb(0 0 0 / 0.6);
}
}
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
font-family: system-ui, sans-serif;
color: var(--teks);
background: var(--bg);
}
.kartu {
width: min(22rem, 90vw);
padding: 1.5rem;
background: var(--surface);
border-radius: var(--radius);
box-shadow: var(--bayangan);
transition: transform var(--durasi) ease, box-shadow var(--durasi) ease;
}
.kartu:hover {
transform: translateY(-6px); /* hanya transform: murah untuk browser */
box-shadow: 0 20px 40px -12px rgb(0 0 0 / 0.3);
}
.judul {
margin: 0 0 0.5rem;
font-size: 1.75rem;
background: linear-gradient(135deg, var(--utama), var(--aksen));
-webkit-background-clip: text; /* masih dibutuhkan sebagian browser berbasis WebKit */
background-clip: text;
color: transparent;
}
.foto {
width: 100%;
height: auto;
aspect-ratio: 16 / 9;
object-fit: cover;
border-radius: calc(var(--radius) - 6px);
filter: grayscale(1);
transition: filter var(--durasi) ease;
}
.kartu:hover .foto { filter: grayscale(0); } /* warna muncul saat hover */
.badge {
display: inline-block;
padding: 0.2rem 0.7rem;
font-size: 0.8rem;
color: white;
background: var(--aksen);
border-radius: 9999px;
animation: denyut 1.6s ease-in-out infinite;
}
@keyframes denyut {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.08); }
}
.muncul { animation: masuk 600ms ease-out both; }
@keyframes masuk {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
</style>
</head>
<body>
<article class="kartu muncul">
<img class="foto" src="https://images.unsplash.com/photo-1509042239860-f550ce710b93?w=800"
alt="Secangkir kopi hitam di atas meja" width="800" height="450">
<h2 class="judul">Kopi Pagi</h2>
<span class="badge">Promo</span>
<p>Arahkan kursor ke kartu untuk melihat efeknya.</p>
</article>
</body>
</html>
<!-- → Kartu meluncur masuk dari bawah saat halaman dibuka.
Hover: kartu terangkat 6px, bayangan menebal, foto berubah dari hitam-putih ke berwarna.
Ganti OS ke dark mode → seluruh warna berganti otomatis tanpa mengubah CSS komponen. -->
Kesalahan umum
transition: all. Semua properti ikut beranimasi, termasuk yang tidak diinginkan. Cara benar: sebutkan properti spesifik.- Menganimasikan
width,left, ataumarginuntuk efek gerak. Cara benar: pakaitransformdanopacity. - Mengabaikan
prefers-reduced-motion. Cara benar: matikan atau kurangi animasi untuk pengguna yang memintanya. - Menulis variabel tanpa
var(), misalnyacolor: --utama;. Cara benar:color: var(--utama);. - Bayangan terlalu pekat seperti
box-shadow: 5px 5px 5px black. Cara benar: blur besar, offset kecil, warna transparan.
Latihan
- Mudah, Buat tombol yang membesar 5% dengan transisi 200ms saat di-hover.
- Sedang, Buat loader berbentuk lingkaran berputar memakai
border,border-radius, dan@keyframesdenganrotate. - Menantang, Buat sistem tema dengan custom properties: tema terang di
:root, tema gelap di[data-theme="dark"], dan tombol JavaScript yang mengganti atributdata-themepada<html>.
Lihat solusi
/* Soal 1 */
.btn-zoom {
padding: 0.75rem 1.5rem;
transition: transform 200ms ease;
}
.btn-zoom:hover { transform: scale(1.05); }
/* Soal 2 */
.loader {
width: 48px;
height: 48px;
border: 5px solid hsl(220 20% 85%);
border-top-color: hsl(245 75% 60%); /* hanya satu sisi berwarna, jadi terlihat berputar */
border-radius: 50%;
animation: putar 0.8s linear infinite;
}
@keyframes putar {
to { transform: rotate(360deg); }
}
<!-- Soal 3 -->
<html lang="id" data-theme="light">
<head>
<style>
:root {
--bg: #ffffff;
--teks: #111827;
}
[data-theme="dark"] {
--bg: #0f172a;
--teks: #f1f5f9;
}
body {
background: var(--bg);
color: var(--teks);
transition: background-color 300ms ease, color 300ms ease;
}
</style>
</head>
<body>
<button id="ganti-tema" type="button">Ganti tema</button>
<script>
const root = document.documentElement;
document.getElementById('ganti-tema').addEventListener('click', () => {
const gelap = root.dataset.theme === 'dark';
root.dataset.theme = gelap ? 'light' : 'dark';
// → klik pertama: halaman jadi gelap, klik kedua: kembali terang
});
</script>
</body>
</html>
Ringkasan
- Transition untuk perubahan bertahap yang dipicu keadaan; animation dan
@keyframesuntuk gerak multi-tahap yang bisa berjalan sendiri. - Animasikan
transformdanopacityuntuk performa terbaik; hormatiprefers-reduced-motion. - Bayangan halus memakai blur besar dan warna transparan; gradient adalah gambar yang dipasang di
background. - Custom properties (
--nama,var()) bisa berubah saat runtime dan mengikuti cascade, dasar dari theming dan dark mode. filtermengubah tampilan elemen itu sendiri,backdrop-filtermemengaruhi apa yang ada di belakangnya.