Micro-interaction adalah animasi singkat yang terjadi saat pengguna berinteraksi dengan elemen UI: hover, klik, submit, dan sebagainya. Efek kecil ini memperlihatkan feedback langsung, membuat website terasa hidup dan profesional, sekaligus meningkatkan trust dan engagement pengguna.
Kenapa Micro-Interaction Penting?
- Memberikan feedback instan saat action terjadi.
- Membantu user memahami status, proses, atau hasil dari suatu aksi.
- Membuat brand, tombol, atau komponen lebih memorable & enjoyable.
Teknik Dasar Micro-Interaction dengan GSAP
1. Button Hover Animation

Alih-alih hanya membesar biasa, kita akan membuat efek "Magnetic Glow Button". Tombol ini akan terasa magnetik (mengikuti kursor mouse sedikit) dan memiliki efek kilauan cahaya yang bergerak mengikuti posisi mouse. Berikut adalah kode lengkapnya
button-magnetic.html
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>1. GSAP Micro-Interaction - Magnetic Glow Button</title>
<style>
/* --- RESET & LAYOUT --- */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
background-color: #0f0c29; /* Background gelap elegan */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
overflow: hidden;
}
h2 {
color: white;
font-weight: 300;
margin-bottom: 50px;
letter-spacing: 2px;
opacity: 0.7;
text-transform: uppercase;
font-size: 1.2rem;
}
/* --- TOMBOL UTAMA --- */
.magnetic-btn {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 25px 60px;
font-size: 1.5rem;
font-weight: bold;
color: white;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 100px; /* Pill shape */
cursor: pointer;
overflow: hidden; /* Penting untuk masking efek glow */
text-decoration: none;
transition: transform 0.1s; /* Smooth transition kecil */
z-index: 1;
}
/* Teks tombol */
.btn-text {
position: relative;
z-index: 2; /* Di atas glow */
pointer-events: none; /* Mouse event tembus ke parent */
}
/* Elemen Glow (Cahaya) */
.btn-glow {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 200px;
background: radial-gradient(
circle,
rgba(255, 255, 255, 0.8) 0%,
rgba(255, 255, 255, 0) 70%
);
border-radius: 50%;
transform: translate(-50%, -50%); /* Center glow pada kursor */
pointer-events: none;
opacity: 0; /* Awalnya tidak terlihat */
mix-blend-mode: overlay; /* Blending mode keren */
transition: opacity 0.3s ease;
}
/* Border Glow Effect (Opsional, menambah kemewahan) */
.magnetic-btn::before {
content: "";
position: absolute;
inset: 0;
border-radius: 100px;
padding: 2px; /* Border width */
background: linear-gradient(45deg, #ff00cc, #333399);
-webkit-mask: linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
opacity: 0.5;
transition: opacity 0.3s ease;
}
.magnetic-btn:hover::before {
opacity: 1;
}
</style>
</head>
<body>
<h2>Hover & Move Mouse</h2>
<a href="#" class="magnetic-btn" id="magneticBtn">
<span class="btn-text">Explore Universe</span>
<div class="btn-glow"></div>
</a>
<!-- Load GSAP -->
<script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
<script>
const btn = document.getElementById("magneticBtn");
const glow = btn.querySelector(".btn-glow");
const text = btn.querySelector(".btn-text");
// === 1. EFEK MAGNETIK (Tombol Mengikuti Mouse) ===
btn.addEventListener("mousemove", (e) => {
// Ambil posisi dan ukuran tombol
const rect = btn.getBoundingClientRect();
// Hitung posisi mouse relatif terhadap tengah tombol
const x = e.clientX - rect.left - rect.width / 2;
const y = e.clientY - rect.top - rect.height / 2;
// Gerakkan tombol sedikit ke arah mouse (Magnetic effect)
// duration: 0.3 memberikan efek 'berat' yang smooth
gsap.to(btn, {
x: x * 0.3, // Faktor kekuatan magnet (0.3 = 30% gerakan mouse)
y: y * 0.3,
duration: 0.3,
ease: "power2.out",
});
// Gerakkan teks sedikit lebih banyak untuk efek Parallax
gsap.to(text, {
x: x * 0.1,
y: y * 0.1,
duration: 0.3,
ease: "power2.out",
});
// === 2. EFEK GLOW (Cahaya Mengikuti Mouse) ===
// Hitung posisi mouse relatif terhadap tombol (pojok kiri atas = 0,0)
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
gsap.to(glow, {
x: mouseX,
y: mouseY,
opacity: 1, // Munculkan glow saat hover
duration: 0.1, // Sangat responsif
ease: "none", // Linear agar menempel tepat di mouse
});
});
// === 3. KEMBALI KE POSISI AWAL (Mouse Leave) ===
btn.addEventListener("mouseleave", () => {
// Reset posisi tombol
gsap.to(btn, {
x: 0,
y: 0,
duration: 0.6, // Lebih lambat saat kembali (elastic feel)
ease: "elastic.out(1, 0.3)", // Efek memantul saat dilepas
});
// Reset posisi teks
gsap.to(text, {
x: 0,
y: 0,
duration: 0.6,
ease: "elastic.out(1, 0.3)",
});
// Sembunyikan glow
gsap.to(glow, {
opacity: 0,
duration: 0.3,
});
});
</script>
</body>
</html>
Penjelasan:
- Efek Magnetik: Tombol tidak diam saja, tapi "tertarik" ke arah kursor Anda. Ini memberikan feedback fisik seolah tombolnya memiliki bobot dan gravitasi.
- Parallax Text: Teks di dalam tombol bergerak dengan kecepatan berbeda dari tombolnya sendiri. Ini menciptakan kedalaman 3D yang halus.
- Dynamic Glow: Ada cahaya (glow) yang mengikuti posisi mouse di dalam tombol. Bukan sekadar hover ganti warna, tapi ada sumber cahaya yang bergerak interaktif.
- Elastic Reset: Saat mouse keluar, tombol tidak langsung snap kembali kaku, tapi memantul sedikit (
elastic.out) seolah dilepas dari pegas magnet. - Desain Premium: Menggunakan gradient border tipis, background semi-transparan, dan typography yang bersih.
2. Card Hover Elevation

Kartu ini tidak hanya "terangkat", tapi akan berotasi 3D mengikuti posisi mouse (efek tilt), memiliki refleksi cahaya (sheen) yang realistis, dan konten di dalamnya akan memiliki efek parallax (kedalaman) yang ekstrem. Ini adalah teknik yang sering dipakai di website showcase produk premium atau NFT.
card-holographic.html
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>2. GSAP Micro-Interaction - 3D Holographic Card</title>
<style>
/* --- RESET & LAYOUT --- */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
background-color: #121212; /* Dark mode premium */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
overflow: hidden;
perspective: 1000px; /* PENTING: Memberikan ruang 3D untuk rotasi */
}
h2 {
color: #fff;
opacity: 0.5;
margin-bottom: 40px;
text-transform: uppercase;
letter-spacing: 2px;
font-weight: 300;
}
/* --- CONTAINER KARTU --- */
.card-container {
width: 320px;
height: 450px;
position: relative;
cursor: pointer;
/* Container ini tidak berotasi, tapi membungkus elemen yang berotasi */
}
/* --- KARTU 3D --- */
.card {
width: 100%;
height: 100%;
background: linear-gradient(145deg, #1e1e1e, #2a2a2a);
border-radius: 20px;
/* Shadow awal yang soft */
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
position: relative;
overflow: hidden;
transform-style: preserve-3d; /* Agar anak elemen ikut 3D */
border: 1px solid rgba(255, 255, 255, 0.1);
}
/* --- KONTEN PARALLAX --- */
.card-content {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 30px;
display: flex;
flex-direction: column;
justify-content: flex-end; /* Teks di bawah */
z-index: 2;
transform: translateZ(
40px
); /* Teks melayang di atas kartu (Parallax) */
pointer-events: none;
}
.card-title {
font-size: 2rem;
font-weight: 800;
color: white;
margin-bottom: 10px;
text-shadow: 0 5px 10px rgba(0, 0, 0, 0.3);
}
.card-desc {
font-size: 1rem;
color: #aaa;
line-height: 1.5;
}
/* --- GAMBAR PRODUK (SEPATU/ITEM) --- */
.card-img {
position: absolute;
top: 20%;
left: 50%;
transform: translate(-50%, 0) translateZ(0px); /* Awalnya nempel di kartu */
width: 250px;
z-index: 1;
transition: all 0.1s ease;
/* Filter drop-shadow untuk bayangan realistis di sepatu */
filter: drop-shadow(0 10px 20px rgba(0, 0, 0, 0.5));
}
/* Placeholder gambar sepatu transparan */
/* Ganti src ini dengan URL gambar sepatu/produk PNG transparan yang bagus */
.card-img img {
width: 100%;
display: block;
}
/* --- EFEK KILAUAN (SHEEN) --- */
.card-sheen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
125deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.05) 40%,
rgba(255, 255, 255, 0.2) 50%,
rgba(255, 255, 255, 0.05) 60%,
rgba(255, 255, 255, 0) 100%
);
z-index: 3;
opacity: 0;
pointer-events: none;
mix-blend-mode: overlay;
}
/* Dekorasi Background Circle */
.circle-bg {
position: absolute;
top: -50px;
right: -50px;
width: 200px;
height: 200px;
background: linear-gradient(135deg, #ff00cc, #333399);
border-radius: 50%;
filter: blur(40px);
opacity: 0.6;
z-index: 0;
}
</style>
</head>
<body>
<h2>Hover to Interact</h2>
<div class="card-container" id="cardContainer">
<div class="card" id="card">
<!-- Dekorasi Background -->
<div class="circle-bg"></div>
<!-- Efek Kilauan -->
<div class="card-sheen"></div>
<!-- Gambar Produk (PNG Transparan Sangat Disarankan) -->
<div class="card-img">
<img
src="<https://pngimg.com/d/running_shoes_PNG5816.png>"
alt="Nike Shoe"
/>
</div>
<!-- Teks -->
<div class="card-content">
<h3 class="card-title">Nike Air</h3>
<p class="card-desc">
Ultra-lightweight design meets futuristic comfort. Run beyond
limits.
</p>
</div>
</div>
</div>
<!-- Load GSAP -->
<script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
<script>
const container = document.getElementById("cardContainer");
const card = document.getElementById("card");
const shoe = document.querySelector(".card-img");
const sheen = document.querySelector(".card-sheen");
const content = document.querySelector(".card-content");
// === 1. MOUSE MOVE (Efek 3D Tilt) ===
container.addEventListener("mousemove", (e) => {
const rect = container.getBoundingClientRect();
// Hitung posisi mouse relatif terhadap tengah kartu
// Range: -1 sampai 1
const x = (e.clientX - rect.left) / rect.width - 0.5;
const y = (e.clientY - rect.top) / rect.height - 0.5;
// ROTASI KARTU
// y mengontrol rotasi X (atas-bawah), x mengontrol rotasi Y (kiri-kanan)
gsap.to(card, {
rotationY: x * 40, // Maksimal putar 40 derajat
rotationX: -y * 40, // Negatif agar mouse ke atas = kartu dongak ke atas
transformPerspective: 1000, // Perspektif lokal
transformOrigin: "center center",
duration: 0.5,
ease: "power2.out",
});
// EFEK PARALLAX PRODUK (Sepatu Keluar)
gsap.to(shoe, {
z: 80, // Sepatu maju ke depan 80px (Pop out)
x: x * -30, // Bergerak berlawanan arah tilt sedikit
y: y * -30,
scale: 1.1, // Sedikit membesar
duration: 0.5,
ease: "power2.out",
});
// EFEK PARALLAX TEKS
gsap.to(content, {
z: 40, // Teks maju 40px (di antara background dan sepatu)
x: x * -20,
y: y * -20,
duration: 0.5,
ease: "power2.out",
});
// EFEK SHEEN (Kilauan Cahaya)
// Kilauan bergerak berlawanan dengan mouse untuk simulasi refleksi
gsap.to(sheen, {
opacity: 1,
backgroundPosition: `${50 + x * 50}% ${50 + y * 50}%`,
duration: 0.1,
ease: "none",
});
// EFEK SHADOW REALISTIS
gsap.to(card, {
boxShadow: `${-x * 30}px ${-y * 30}px 50px rgba(0,0,0,0.4)`,
duration: 0.5,
});
});
// === 2. MOUSE LEAVE (Reset Semua) ===
container.addEventListener("mouseleave", () => {
// Reset Kartu
gsap.to(card, {
rotationY: 0,
rotationX: 0,
boxShadow: "0 10px 30px rgba(0,0,0,0.5)",
duration: 0.8,
ease: "elastic.out(1, 0.5)", // Efek memantul saat reset
});
// Reset Sepatu
gsap.to(shoe, {
z: 0,
x: 0,
y: 0,
scale: 1,
duration: 0.8,
ease: "elastic.out(1, 0.5)",
});
// Reset Teks
gsap.to(content, {
z: 0,
x: 0,
y: 0,
duration: 0.8,
ease: "power2.out",
});
// Hilangkan Sheen
gsap.to(sheen, {
opacity: 0,
duration: 0.5,
});
});
</script>
</body>
</html>
Penjelasan:
- 3D Tilt Realistis: Kartu benar-benar miring mengikuti mouse Anda, bukan sekadar geser 2D. Ini menggunakan properti
perspectiveCSS danrotationX/rotationYGSAP. - Layered Parallax (Pop-Out Effect): Perhatikan bahwa saat Anda hover:
- Kartu miring.
- Teks melayang sedikit (
translateZ: 40px). - Sepatu melayang jauh lebih depan (
translateZ: 80px) bahkan keluar dari bingkai kartu. Ini menciptakan ilusi bahwa sepatu tersebut nyata dan bisa diambil.
- Dynamic Lighting (Sheen): Ada lapisan cahaya (
.card-sheen) yang bergerak dinamis. Ini membuat kartu terasa seperti terbuat dari bahan glossy atau kaca premium yang memantulkan cahaya ruangan. - Dynamic Shadow: Bayangan kartu (
boxShadow) bergerak berlawanan arah dengan kursor. Jika mouse di kiri, bayangan jatuh ke kanan. Detail kecil ini sangat menipu mata untuk percaya bahwa objek itu 3D. - Elastic Reset: Sama seperti tombol sebelumnya, saat mouse pergi, kartu memantul kembali ke posisi netral dengan elegan.
3. Ripple Click Effect

Biasanya efek ripple (seperti di Material Design) hanya berupa lingkaran putih sederhana yang membesar lalu hilang. Tapi kita akan membuatnya "Liquid Ripple".
Efek ini akan membuat tombol terasa seperti permukaan air yang tenang, dan saat diklik, akan ada gelombang air yang nyata (distorsi) menyebar dari titik klik, disertai dengan partikel-partikel kecil yang meledak (burst).
button-ripple-liquid.html
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>3. GSAP Micro-Interaction - Liquid Ripple</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
background-color: #1a1a1a;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
overflow: hidden;
}
h2 {
position: absolute;
top: 15%;
color: rgba(255, 255, 255, 0.3);
text-transform: uppercase;
letter-spacing: 3px;
}
/* --- TOMBOL UTAMA --- */
.liquid-btn {
position: relative;
padding: 25px 80px;
font-size: 1.5rem;
font-weight: bold;
color: white;
background: #4a00e0; /* Fallback color */
background: linear-gradient(135deg, #8e2de2, #4a00e0);
border: none;
border-radius: 100px;
cursor: pointer;
overflow: hidden; /* Wajib untuk menahan ripple di dalam */
transition: transform 0.1s;
box-shadow: 0 10px 30px rgba(74, 0, 224, 0.4);
/* Hilangkan highlight biru di mobile */
-webkit-tap-highlight-color: transparent;
user-select: none;
}
.liquid-btn:active {
transform: scale(0.95); /* Efek tekan */
}
/* Teks Tombol */
.btn-content {
position: relative;
z-index: 5;
pointer-events: none;
}
/* --- ELEMEN RIPPLE --- */
.ripple {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.6);
transform: scale(0);
pointer-events: none;
z-index: 1;
}
/* --- ELEMEN PARTIKEL (BURST) --- */
.particle {
position: absolute;
width: 8px;
height: 8px;
background: white;
border-radius: 50%;
pointer-events: none;
z-index: 4;
}
</style>
</head>
<body>
<h2>Click Anywhere on Button</h2>
<button class="liquid-btn" id="rippleBtn">
<span class="btn-content">Click Me</span>
</button>
<!-- Load GSAP -->
<script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
<script>
const btn = document.getElementById("rippleBtn");
btn.addEventListener("click", function (e) {
// 1. MENDAPATKAN POSISI KLIK
const rect = btn.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// === EFEK 1: SHOCKWAVE RIPPLE ===
// Kita buat elemen span baru secara dinamis
const ripple = document.createElement("span");
ripple.classList.add("ripple");
// Ukuran ripple harus cukup besar untuk menutupi tombol
const diameter = Math.max(rect.width, rect.height) * 2;
ripple.style.width = ripple.style.height = `${diameter}px`;
// Posisi ripple di tengah kursor
ripple.style.left = `${x - diameter / 2}px`;
ripple.style.top = `${y - diameter / 2}px`;
btn.appendChild(ripple);
// Animasikan Ripple dengan GSAP
gsap.to(ripple, {
scale: 1, // Membesar memenuhi tombol
opacity: 0, // Sambil menghilang
duration: 0.8,
ease: "power2.out",
onComplete: () => {
ripple.remove(); // Hapus elemen dari DOM setelah selesai (PENTING!)
},
});
// === EFEK 2: PARTICLE BURST (LEDAKAN PARTIKEL) ===
// Kita buat 8-12 partikel kecil yang meledak dari titik klik
const particleCount = 12;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement("span");
particle.classList.add("particle");
// Posisi awal partikel di titik klik
particle.style.left = `${x}px`;
particle.style.top = `${y}px`;
// Warna random untuk variasi (Putih, Ungu Muda, Biru Muda)
const colors = ["#ffffff", "#e0c3fc", "#8ec5fc"];
particle.style.backgroundColor =
colors[Math.floor(Math.random() * colors.length)];
// Ukuran random
const size = Math.random() * 6 + 4; // 4px - 10px
particle.style.width = particle.style.height = `${size}px`;
btn.appendChild(particle);
// Hitung arah ledakan random (Angle & Distance)
const angle = Math.random() * Math.PI * 2; // 360 derajat
const velocity = Math.random() * 60 + 30; // Jarak ledakan 30px - 90px
const destX = Math.cos(angle) * velocity;
const destY = Math.sin(angle) * velocity;
// Animasi Partikel
gsap.to(particle, {
x: destX,
y: destY,
opacity: 0, // Menghilang di akhir
scale: 0, // Mengecil sampai habis
duration: Math.random() * 0.5 + 0.4, // Durasi random 0.4s - 0.9s
ease: "power2.out",
onComplete: () => {
particle.remove();
},
});
}
// === EFEK 3: TEXT SHAKE (Guncangan Teks) ===
// Teks sedikit terguncang karena "impact" klik
const content = btn.querySelector(".btn-content");
gsap.fromTo(
content,
{ x: 0 },
{
x: 3,
duration: 0.1,
repeat: 3,
yoyo: true,
ease: "sine.inOut",
onComplete: () => gsap.to(content, { x: 0 }), // Reset
}
);
});
// BONUS: HOVER SHINE EFFECT
// Menambahkan efek kilauan halus saat mouse bergerak di atas tombol
btn.addEventListener("mousemove", (e) => {
const rect = btn.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
// Kita gunakan background radial gradient untuk simulasi cahaya
// Layer 1: Warna dasar, Layer 2: Cahaya putih transparan mengikuti mouse
gsap.to(btn, {
background: `radial-gradient(circle at ${x}px ${y}px, rgba(255,255,255,0.3), rgba(255,255,255,0) 100px), linear-gradient(135deg, #8e2de2, #4a00e0)`,
duration: 0.2,
});
});
btn.addEventListener("mouseleave", () => {
gsap.to(btn, {
background: `linear-gradient(135deg, #8e2de2, #4a00e0)`,
duration: 0.5,
});
});
</script>
</body>
</html>
Penjelasan:
- Titik Awal Dinamis: Ripple tidak muncul dari tengah tombol, tapi tepat dari koordinat
(x, y)di mana mouse Anda mengklik. Ini membuat interaksi terasa sangat akurat dan responsif. - Particle Burst: Ini adalah detail yang sering terlewatkan. Ledakan partikel kecil menambah kesan "energi" pada klik. Tombol terasa "juicy" (istilah game design untuk UI yang memuaskan).
- Self-Cleaning: Script ini secara otomatis menghapus (
.remove()) elemen ripple dan partikel dari DOM setelah animasi selesai. Ini sangat penting untuk performa website agar halaman tidak berat karena ribuan elemen sampah tak terlihat (memory leak). - Impact Feedback: Teks di dalam tombol bergetar sedikit saat diklik, memberikan ilusi bahwa tombol tersebut benar-benar menerima tekanan fisik.
- Interactive Shine: Saat tidak diklik pun (hanya di-hover), ada efek cahaya senter (flashlight) yang mengikuti mouse, membuat tombol terasa hidup bahkan sebelum interaksi utama terjadi.
4. Icon Toggle Animation (Like/Bookmark)

Efek Like/Heart seperti di Twitter atau Instagram sangat memuaskan karena penuh dengan ledakan warna (confetti). Kita akan membuat versi GSAP-nya yang super smooth.
Fitur utamanya:
- Elastic Bounce: Ikon hati akan memantul drastis seperti karet.
- Confetti Explosion: Ledakan lingkaran warna-warni di sekitar hati.
- Toggle State: Bisa di-klik lagi untuk unlike (warna abu-abu).
icon-toggle-heart.html
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>4. GSAP Micro-Interaction - Heart Explosion</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #ffe6ea; /* Pink pastel background */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
overflow: hidden;
}
h2 {
color: #d6336c;
margin-bottom: 40px;
font-weight: 600;
opacity: 0.8;
}
/* --- WADAH IKON --- */
.icon-wrapper {
position: relative;
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
/* Hilangkan highlight biru di mobile */
-webkit-tap-highlight-color: transparent;
}
/* --- SVG HEART --- */
.heart-svg {
width: 60px;
height: 60px;
fill: transparent; /* Awalnya kosong */
stroke: #888; /* Warna outline abu-abu */
stroke-width: 4px;
transition: stroke 0.3s;
/* Origin di tengah untuk scaling yang rapi */
transform-origin: center center;
}
.icon-wrapper:hover .heart-svg {
stroke: #d6336c; /* Hover jadi pink */
}
/* --- CONFETTI DOTS --- */
/* Kita siapkan elemen titik-titik ini di sekitar ikon */
.confetti-group {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
pointer-events: none; /* Klik tembus ke ikon */
}
.dot {
position: absolute;
width: 8px;
height: 8px;
border-radius: 50%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0); /* Awalnya tersembunyi di tengah */
}
</style>
</head>
<body>
<h2>Click the Heart!</h2>
<div class="icon-wrapper" id="likeBtn">
<!-- Confetti (8 titik melingkar) -->
<div class="confetti-group">
<div class="dot" style="background: #ff4d6d;"></div>
<div class="dot" style="background: #ffb703;"></div>
<div class="dot" style="background: #06d6a0;"></div>
<div class="dot" style="background: #118ab2;"></div>
<div class="dot" style="background: #ff4d6d;"></div>
<div class="dot" style="background: #ffb703;"></div>
<div class="dot" style="background: #06d6a0;"></div>
<div class="dot" style="background: #118ab2;"></div>
</div>
<!-- SVG Heart Icon -->
<svg class="heart-svg" viewBox="0 0 24 24">
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
</svg>
</div>
<!-- Load GSAP -->
<script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
<script>
const btn = document.getElementById('likeBtn');
const heart = btn.querySelector('.heart-svg');
const dots = btn.querySelectorAll('.dot');
let isLiked = false;
// Timeline utama agar animasi bisa dikontrol dengan mudah
const tl = gsap.timeline({ paused: true });
// --- 1. SETUP ANIMASI "LIKE" (Timeline) ---
// A. Hati Mengkerut Dulu (Anticipation)
tl.to(heart, {
scale: 0.6,
duration: 0.1,
ease: "power2.out"
})
// B. Hati Meledak Besar & Berubah Warna
.to(heart, {
scale: 1.2,
fill: "#e94560", // Isi warna merah/pink
stroke: "#e94560", // Outline juga merah
duration: 0.4,
ease: "elastic.out(1, 0.3)" // Memantul kenyal
})
// C. Hati Kembali ke Ukuran Normal
.to(heart, {
scale: 1,
duration: 0.2,
ease: "power2.out"
}, "-=0.2"); // Mulai sedikit sebelum animasi B selesai
// --- 2. SETUP ANIMASI CONFETTI ---
// Kita sebarkan titik-titik ke arah melingkar
dots.forEach((dot, i) => {
// Hitung sudut untuk 8 titik (360 / 8 = 45 derajat)
const angle = (i / dots.length) * Math.PI * 2;
const radius = 40; // Jarak ledakan
const x = Math.cos(angle) * radius;
const y = Math.sin(angle) * radius;
// Tambahkan animasi confetti ke timeline yang sama
// Dimulai saat Hati Meledak (label atau insert point 0.1 detik)
tl.to(dot, {
x: x,
y: y,
scale: 1, // Membesar
duration: 0.4,
ease: "power3.out"
}, 0.1)
// Fade Out confetti di akhir
.to(dot, {
opacity: 0,
scale: 0, // Mengecil lagi
duration: 0.3,
}, ">-0.2"); // Setelah animasi sebelumnya hampir selesai
});
// --- 3. INTERAKSI KLIK ---
btn.addEventListener('click', () => {
if (!isLiked) {
// === ACTION: LIKE ===
tl.restart(); // Mainkan animasi dari awal
isLiked = true;
} else {
// === ACTION: UNLIKE ===
// Kita buat animasi manual untuk unlike (lebih simpel, sedih, abu-abu)
// Reset posisi confetti instan
gsap.set(dots, { x: 0, y: 0, scale: 0, opacity: 1 });
gsap.to(heart, {
scale: 0.8,
duration: 0.1,
onComplete: () => {
gsap.to(heart, {
scale: 1,
fill: "transparent", // Kosongkan lagi
stroke: "#888", // Warna abu lagi
duration: 0.3,
ease: "power2.out"
});
}
});
isLiked = false;
}
});
</script>
</body>
</html>
Penjelasan:
- Anticipation (Antisipasi): Sebelum meledak besar, hati mengecil dulu (
scale: 0.6). Ini adalah prinsip dasar animasi Disney (Squash & Stretch) yang membuat gerakan terasa bertenaga. - Elasticity: Saat membesar, ia menggunakan
elastic.out. Gerakannya tidak linear, tapi wobble (bergetar) seperti jeli, memberikan kesan organik dan playful. - Confetti Burst: Ledakan titik-titik warna-warni di sekeliling ikon adalah kunci kepuasan visual. Tanpa ini, like hanyalah perubahan warna biasa.
- Matematika Trigonometri: Kita menggunakan
Math.cosdanMath.sinuntuk menyebarkan partikel membentuk lingkaran sempurna secara otomatis, berapapun jumlah partikelnya. Anda bisa ubah 8 titik jadi 12 titik tanpa merusak layout. - State Management: Script ini menangani logika Like vs Unlike dengan animasi yang berbeda. Animasi Like heboh dan berwarna, animasi Unlike lebih "kalem" dan kembali ke abu-abu.
5. Submit Button Feedback

Ini adalah teknik yang paling kompleks namun paling fungsional. Tombol Submit yang baik tidak membiarkan pengguna menunggu dalam ketidakpastian.
Kita akan membuat tombol yang:
- Morph: Berubah bentuk dari tombol lebar menjadi lingkaran kecil saat diklik.
- Load: Menampilkan animasi loading berputar.
- Success: Berubah menjadi ikon centang hijau dengan efek draw SVG yang memuaskan.
- Reset: Kembali ke bentuk tombol awal setelah beberapa detik.
submit-feedback.html
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>5. GSAP Micro-Interaction - Morphing Submit Button</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f4f7f6;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
/* --- CONTAINER TOMBOL --- */
.btn-container {
position: relative;
width: 200px; /* Lebar awal */
height: 60px; /* Tinggi tetap */
display: flex;
align-items: center;
justify-content: center;
}
/* --- TOMBOL UTAMA --- */
#submitBtn {
width: 100%;
height: 100%;
border: none;
border-radius: 50px;
background: #2c3e50;
color: white;
font-size: 1.2rem;
font-weight: bold;
cursor: pointer;
outline: none;
position: absolute;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 10px 20px rgba(44, 62, 80, 0.3);
/* Transisi CSS hanya untuk hover state sederhana */
transition: transform 0.1s, background 0.3s;
}
#submitBtn:hover {
background: #34495e;
transform: translateY(-2px);
}
/* --- TEKS TOMBOL --- */
.btn-text {
position: absolute;
white-space: nowrap;
}
/* --- LOADING SPINNER (SVG) --- */
.spinner {
position: absolute;
width: 30px;
height: 30px;
opacity: 0; /* Tersembunyi awal */
visibility: hidden;
}
.spinner circle {
fill: none;
stroke: white;
stroke-width: 4;
stroke-linecap: round;
stroke-dasharray: 60;
stroke-dashoffset: 60; /* Untuk animasi draw */
transform-origin: center;
}
/* --- SUCCESS CHECKMARK (SVG) --- */
.check-icon {
position: absolute;
width: 30px;
height: 30px;
opacity: 0;
visibility: hidden;
}
.check-icon path {
fill: none;
stroke: white;
stroke-width: 4;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 50; /* Panjang path centang (kira-kira) */
stroke-dashoffset: 50; /* Awalnya tersembunyi */
}
</style>
</head>
<body>
<div class="btn-container">
<button id="submitBtn">
<span class="btn-text">Submit Order</span>
<!-- SVG Spinner -->
<svg class="spinner" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="20"></circle>
</svg>
<!-- SVG Checkmark -->
<svg class="check-icon" viewBox="0 0 50 50">
<path d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
</svg>
</button>
</div>
<!-- Load GSAP -->
<script src="<https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js>"></script>
<script>
const btn = document.getElementById('submitBtn');
const text = btn.querySelector('.btn-text');
const spinner = btn.querySelector('.spinner');
const circle = spinner.querySelector('circle');
const check = btn.querySelector('.check-icon');
const checkPath = check.querySelector('path');
let isAnimating = false;
btn.addEventListener('click', () => {
if (isAnimating) return; // Cegah double click
isAnimating = true;
// Buat Timeline utama
const tl = gsap.timeline();
// === TAHAP 1: MORPH KE LINGKARAN & LOADING ===
// 1. Hilangkan Teks
tl.to(text, { opacity: 0, duration: 0.3 })
// 2. Kecilkan Tombol jadi Lingkaran (Lebar 200px -> 60px)
.to(btn, {
width: "60px",
borderRadius: "50%", // Pastikan bulat sempurna
backgroundColor: "#2c3e50", // Tetap warna gelap
duration: 0.5,
ease: "power2.inOut"
}, "-=0.2") // Mulai sebelum teks hilang total
// 3. Tampilkan Spinner & Putar
.to(spinner, {
opacity: 1,
visibility: "visible",
duration: 0.2
})
// Animasi putar spinner (Looping sementara)
.to(spinner, {
rotation: 360,
repeat: 2, // Putar 2 kali (simulasi loading)
duration: 1,
ease: "none"
})
// Animasi stroke circle (mengisi lingkaran)
.to(circle, {
strokeDashoffset: 0,
duration: 1,
ease: "none"
}, "<") // Bersamaan dengan rotasi
// === TAHAP 2: SUKSES & CENTANG ===
// 4. Sembunyikan Spinner
.to(spinner, { opacity: 0, duration: 0.2 })
// 5. Ubah Warna Tombol jadi Hijau
.to(btn, {
backgroundColor: "#27ae60",
scale: 1.1, // Sedikit membesar efek "Pop"
duration: 0.3,
ease: "back.out(1.7)"
})
// 6. Tampilkan Icon Centang & Gambar Garisnya
.to(check, {
opacity: 1,
visibility: "visible",
duration: 0.1
}, "<")
.to(checkPath, {
strokeDashoffset: 0, // Gambar garis centang
duration: 0.4,
ease: "power2.out"
})
// 7. Kembali ke Ukuran Normal (Scale 1)
.to(btn, { scale: 1, duration: 0.2 });
// === TAHAP 3: RESET (Setelah beberapa detik) ===
// Delay 2 detik agar user bisa lihat status sukses
gsap.delayedCall(4, () => {
const resetTl = gsap.timeline();
// 1. Hilangkan Centang
resetTl.to(check, { opacity: 0, duration: 0.3 })
.to(checkPath, { strokeDashoffset: 50, duration: 0 }) // Reset path
// 2. Kembalikan Bentuk Tombol Panjang
.to(btn, {
width: "200px",
backgroundColor: "#2c3e50",
borderRadius: "50px",
duration: 0.5,
ease: "power2.inOut"
})
// 3. Tampilkan Teks Kembali
.to(text, { opacity: 1, duration: 0.3 });
// Reset variabel state
resetTl.eventCallback("onComplete", () => {
isAnimating = false;
// Reset properti lain yang mungkin berubah
gsap.set(spinner, { rotation: 0 });
gsap.set(circle, { strokeDashoffset: 60 });
});
});
});
</script>
</body>
</html>
Penjelasan:
- Seamless Transformation: Perubahan dari bentuk persegi panjang (
width: 200px) ke lingkaran (width: 60px) terjadi sangat mulus. Ini menjaga kontinuitas visual di mata user. - State Awareness: Animasi ini merepresentasikan 3 status yang jelas:
- Idle: Siap diklik.
- Loading: Spinner berputar (memberi tahu sistem sedang bekerja).
- Success: Warna hijau + Ikon centang (konfirmasi visual bahwa tugas selesai).
- SVG Stroke Animation: Teknik
strokeDashoffsetdigunakan untuk "menggambar" garis centang secara real-time. Ini terlihat jauh lebih elegan daripada sekadar memunculkan gambar centang statis (fade-in). - Color Psychology: Transisi warna dari Biru Gelap (Netral/Aksi) ke Hijau Cerah (Sukses) memberikan kepuasan instan dan konfirmasi positif.
- Auto Reset: Script ini pintar karena bisa mereset dirinya sendiri. Ini sangat penting untuk UX form di mana user mungkin perlu melakukan submit ulang atau submit data baru tanpa me-refresh halaman.
Tips Micro-Interaction dengan GSAP
- Keep it fast: Usahakan durasi interaksi ≤ 400ms agar terasa responsif.
- Feedback jelas: Gunakan perubahan warna, scale, shadow, atau icon untuk feedback yang gamblang.
- Easing matters: Gunakan
power2,power3,elastic, ataubackuntuk efek organic dan tidak kaku. - Accessibility: Jangan andalkan animasi sebagai satu-satunya feedback. Pastikan tombol tetap bisa diakses keyboard dan screen reader.
Micro-interaction bukan sekadar hiasan visual, melainkan elemen fundamental yang menjembatani komunikasi antara pengguna dan sistem. Melalui seri tutorial ini, kita telah membuktikan bahwa GSAP mampu mengubah interaksi web yang kaku menjadi pengalaman yang "hidup", responsif, dan penuh emosi.
Dengan menguasai teknik-teknik ini, mulai dari efek hover magnetik hingga feedback formulir yang cerdas, Anda tidak hanya sedang membuat website, tetapi sedang merancang pengalaman. Micro-interaction adalah "bumbu rahasia" yang membedakan website standar dari website kelas dunia.
Langkah Selanjutnya:
Gunakan skrip-skrip "mewah" yang telah kita buat sebagai fondasi. Jangan ragu untuk bereksperimen dengan nilai easing, durasi, atau warna agar sesuai dengan identitas brand proyek Anda selanjutnya. Selamat berkarya!