Satu tombol download kecil yang bisa bikin website terasa lebih modern dan “serius dikerjakan”
Hello, friends,
Di ryanpratama.com, saya cukup sering ngobrol soal website, hosting, desain, dan ekosistem digital.
Salah satu detail kecil yang sering diremehkan tapi sebenarnya penting adalah tombol download. Biasanya kita cuma pakai tombol biru polos, teks “Download”, selesai.
Fungsional, tapi ya… biasa banget.
Padahal, dengan sedikit sentuhan animasi, tombol download bisa jadi:
- Lebih menarik untuk diklik
- Terasa lebih profesional
- Ngasih feedback visual yang jelas ke user (“lagi mengunduh”, “berhasil”)
Di tulisan ini, saya mau ajak teman bongkar satu contoh tombol download keren dengan animasi dari CodePen milik Aaron Iker yang menurut saya keren dan wajib saya dokumentasikan kodenya.
Kalau mau bisa kita adaptasikan supaya gampang dipasang di website sendiri.
Berikut previewnya dari codepen
See the Pen Download Button Animation by Aaron Iker (@aaroniker) on CodePen.
Table of Contents
Konsep tombol download yang akan kita pakai
Tombol ini punya beberapa karakter:
- Ada teks bertahap: Download → Downloading → Open File
- Ada icon di sebelah kanan yang berubah jadi tanda ceklis saat selesai
- Ada efek “fill” di kotak kanan (indikasi progress)
- Ada beberapa variasi style:
- versi default (biru)
- dark single
- white single
- dark penuh
Semua ini diatur pakai kombinasi:
- HTML sederhana
- CSS (hasil konversi dari SCSS)
- JavaScript + GSAP untuk animasi SVG dan transisi
Struktur HTML tombol
Ini struktur dasar HTML yang bisa teman pakai di mana saja (WordPress, HTML biasa, dsb):
<div class="container">
<!-- Tombol utama dengan teks 3 tahap -->
<a href="#" class="button">
<ul>
<li>Download</li>
<li>Downloading</li>
<li>Open File</li>
</ul>
<div>
<svg viewBox="0 0 24 24"></svg>
</div>
</a>
<!-- Tombol icon saja - dark -->
<a href="#" class="button dark-single">
<div>
<svg viewBox="0 0 24 24"></svg>
</div>
</a>
<div></div>
<!-- Tombol icon saja - white -->
<a href="#" class="button white-single">
<div>
<svg viewBox="0 0 24 24"></svg>
</div>
</a>
<!-- Tombol dark dengan teks 3 tahap -->
<a href="#" class="button dark">
<ul>
<li>Download</li>
<li>Downloading</li>
<li>Open File</li>
</ul>
<div>
<svg viewBox="0 0 24 24"></svg>
</div>
</a>
</div>
<!-- Kredit kecil (opsional) -->
<a class="dribbble" href="https://dribbble.com/shots/7299868-Download-Buttons" target="_blank">
<img src="https://cdn.dribbble.com/assets/dribbble-ball-mark-2bd45f09c2fb58dbbfb44766d5d1d07c5a12972d602ef8b32204d28fa3dda554.svg" alt="">
</a>
Beberapa catatan:
- Teks di dalam
<ul>akan dianimasikan naik-turun, sehingga terlihat seakan-akan berubah status. - SVG di dalam tombol kanan akan kita isi lewat JavaScript (garis → ceklis).
CSS: gaya tombol + animasi dasar
Di CodePen, style-nya ditulis dalam SCSS. Di bawah ini sudah saya rapikan jadi CSS murni yang siap dipakai di browser tanpa proses build tambahan.
/* Variasi tombol */
.button.dark-single {
--background: none;
--rectangle: #242836;
--success: #4BC793;
}
.button.white-single {
--background: none;
--rectangle: #F5F9FF;
--arrow: #275efe;
--success: #275efe;
--shadow: rgba(10, 22, 50, .1);
}
.button.dark {
--background: #242836;
--rectangle: #1C212E;
--arrow: #F5F9FF;
--text: #F5F9FF;
--success: #2F3545;
}
/* Style dasar tombol */
.button {
--background: #275efe;
--rectangle: #184fee;
--success: #4672F1; /* hasil mix(white, #184fee, 20%) */
--text: #fff;
--arrow: #fff;
--checkmark: #fff;
--shadow: rgba(10, 22, 50, .24);
display: flex;
overflow: hidden;
text-decoration: none;
-webkit-mask-image: -webkit-radial-gradient(white, black);
background: var(--background);
border-radius: 8px;
box-shadow: 0 2px 8px -1px var(--shadow);
transition: transform .2s ease, box-shadow .2s ease;
}
.button:active {
transform: scale(.95);
box-shadow: 0 1px 4px -1px var(--shadow);
}
/* Teks 3 tahap di dalam tombol */
.button ul {
margin: 0;
padding: 16px 40px;
list-style: none;
text-align: center;
position: relative;
backface-visibility: hidden;
font-size: 16px;
font-weight: 500;
line-height: 28px;
color: var(--text);
}
.button ul li:not(:first-child) {
top: 16px;
left: 0;
right: 0;
position: absolute;
}
.button ul li:nth-child(2) {
top: 76px;
}
.button ul li:nth-child(3) {
top: 136px;
}
/* Kotak kanan (icon + background animasi) */
.button > div {
position: relative;
width: 60px;
height: 60px;
background: var(--rectangle);
}
.button > div::before,
.button > div::after {
content: '';
display: block;
position: absolute;
}
.button > div::before {
border-radius: 1px;
width: 2px;
top: 50%;
left: 50%;
height: 17px;
margin: -9px 0 0 -1px;
background: var(--arrow);
}
/* Layer animasi “progress” */
.button > div::after {
width: 60px;
height: 60px;
transform-origin: 50% 0;
border-radius: 0 0 80% 80%;
background: var(--success);
top: 0;
left: 0;
transform: scaleY(0);
}
/* Icon SVG di dalam kotak */
.button > div svg {
display: block;
position: absolute;
width: 20px;
height: 20px;
left: 50%;
top: 50%;
margin: -9px 0 0 -10px;
fill: none;
z-index: 1;
stroke-width: 2px;
stroke: var(--arrow);
stroke-linecap: round;
stroke-linejoin: round;
}
/* State loading: trigger animasi */
.button.loading ul {
animation: text calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
}
.button.loading > div::before {
animation: line calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
}
.button.loading > div::after {
animation: background calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
}
.button.loading > div svg {
animation: svg calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
}
/* Animasi teks (slide naik) */
@keyframes text {
10%,
85% {
transform: translateY(-100%);
}
95%,
100% {
transform: translateY(-200%);
}
}
/* Animasi garis vertikal */
@keyframes line {
5%,
10% {
transform: translateY(-30px);
}
40% {
transform: translateY(-20px);
}
65% {
transform: translateY(0);
}
75%,
100% {
transform: translateY(30px);
}
}
/* Animasi icon SVG (garis → ceklis) */
@keyframes svg {
0%,
20% {
stroke-dasharray: 0;
stroke-dashoffset: 0;
}
21%,
89% {
stroke-dasharray: 26px;
stroke-dashoffset: 26px;
stroke-width: 3px;
margin: -10px 0 0 -10px;
stroke: var(--checkmark);
}
100% {
stroke-dasharray: 26px;
stroke-dashoffset: 0;
margin: -10px 0 0 -10px;
stroke: var(--checkmark);
}
12% {
opacity: 1;
}
20%,
89% {
opacity: 0;
}
90%,
100% {
opacity: 1;
}
}
/* Animasi background progress */
@keyframes background {
10% {
transform: scaleY(0);
}
40% {
transform: scaleY(.15);
}
65% {
transform: scaleY(.5);
border-radius: 0 0 50% 50%;
}
75% {
border-radius: 0 0 50% 50%;
}
90%,
100% {
border-radius: 0;
}
75%,
100% {
transform: scaleY(1);
}
}
/* Reset & layout dasar halaman */
html {
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
body {
min-height: 100vh;
display: flex;
font-family: 'Roboto', Arial, sans-serif;
justify-content: center;
align-items: center;
background: #E4ECFA;
}
/* Wrapper tombol */
body .container {
display: flex;
flex-wrap: wrap;
justify-content: center;
}
body .container > div {
flex-basis: 100%;
width: 0;
}
body .container .button {
margin: 16px;
}
@media (max-width: 400px) {
body .container .button {
margin: 12px;
}
}
/* Kredit Dribbble (opsional) */
body .dribbble {
position: fixed;
display: block;
right: 20px;
bottom: 20px;
}
body .dribbble img {
display: block;
height: 28px;
}
JavaScript: menghidupkan animasi dengan GSAP
Bagian paling “ajaib” ada di JavaScript. Di sini kita:
- Menentukan durasi animasi
- Menggambar path SVG (garis → ceklis) secara dinamis
- Menambahkan class
.loadingke tombol saat diklik - Menggunakan GSAP untuk mengatur smoothing dan pergerakan garis
Pastikan teman menambahkan GSAP terlebih dahulu:
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
Lalu tambahkan script berikut (boleh di bawah HTML, sebelum </body>):
<script>
document.querySelectorAll('.button').forEach(button => {
let duration = 3000,
svg = button.querySelector('svg'),
svgPath = new Proxy({
y: null,
smoothing: null
}, {
set(target, key, value) {
target[key] = value;
if (target.y !== null && target.smoothing !== null) {
svg.innerHTML = getPath(target.y, target.smoothing, null);
}
return true;
},
get(target, key) {
return target[key];
}
});
button.style.setProperty('--duration', duration);
svgPath.y = 20;
svgPath.smoothing = 0;
button.addEventListener('click', e => {
e.preventDefault();
if (!button.classList.contains('loading')) {
button.classList.add('loading');
gsap.to(svgPath, {
smoothing: .3,
duration: (duration * .065) / 1000
});
gsap.to(svgPath, {
y: 12,
duration: (duration * .265) / 1000,
delay: (duration * .065) / 1000,
ease: "elastic.out(1.12, 0.4)"
});
setTimeout(() => {
svg.innerHTML = getPath(0, 0, [
[3, 14],
[8, 19],
[21, 6]
]);
}, duration / 2);
}
});
});
function getPoint(point, i, a, smoothing) {
let cp = (current, previous, next, reverse) => {
let p = previous || current,
n = next || current,
o = {
length: Math.sqrt(Math.pow(n[0] - p[0], 2) + Math.pow(n[1] - p[1], 2)),
angle: Math.atan2(n[1] - p[1], n[0] - p[0])
},
angle = o.angle + (reverse ? Math.PI : 0),
length = o.length * smoothing;
return [
current[0] + Math.cos(angle) * length,
current[1] + Math.sin(angle) * length
];
},
cps = cp(a[i - 1], a[i - 2], point, false),
cpe = cp(point, a[i - 1], a[i + 1], true);
return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}`;
}
function getPath(update, smoothing, pointsNew) {
let points = pointsNew ? pointsNew : [
[4, 12],
[12, update],
[20, 12]
],
d = points.reduce((acc, point, i, a) =>
i === 0
? `M ${point[0]},${point[1]}`
: `${acc} ${getPoint(point, i, a, smoothing)}`,
''
);
return `<path d="${d}" />`;
}
</script>
Cara pakai di WordPress atau proyek teman
Kurang lebih langkahnya seperti ini:
- Copy HTML ke:
- Halaman custom HTML
- Atau ke template theme (misalnya
single.php,page.php)
- Copy CSS ke:
- Customizer → Additional CSS, atau
- File CSS tema/child theme, atau
- File CSS di dalam builder (Elementor, dsb)
- Tambah GSAP + Script:
- Tempel
<script src="...gsap.min.js"></script>dan script JavaScript di footer theme, atau - Gunakan plugin “Insert Headers and Footers” lalu taruh di bagian footer scripts.
- Tempel
- Ubah teks sesuai kebutuhan:
- “Download” → “Download CV”
- “Open File” → “Buka File” atau “Lihat Hasil”
- Kalau mau, teman bisa:
- Menonaktifkan beberapa tombol dan hanya pakai satu variasi
- Ubah warna variabel CSS (
--background,--rectangle, dsb) supaya sesuai brand warna website.
Kenapa detail kecil seperti ini layak dikerjakan?
Buat saya, tombol seperti ini bukan sekadar “gaya-gayaan”.
Dia memberi:
- Feedback yang jelas: user tahu kapan proses dimulai dan kapan selesai.
- Rasa “niat”: memberi kesan bahwa pemilik website peduli dengan pengalaman pengunjung.
- Branding halus: lewat warna dan gerakan, website terasa lebih hidup dan modern.
Tentu saja, teman tidak wajib pakai tombol animasi seperti ini di semua halaman. Tapi di beberapa titik penting:
- Halaman download resource gratis
- Tombol download e-book atau file penting
- Halaman portfolio (download CV, proposal, dsb.)
…tombol seperti ini bisa jadi sentuhan kecil yang membuat website terasa beda.
Pada akhirnya, saya percaya desain web yang baik sering lahir dari perpaduan:
- fungsi yang jelas,
- ditambah detail kecil yang memperkaya pengalaman.
Kalau teman tertarik ngoprek lebih jauh, sumber asli tombol ini ada di
CodePen:
https://codepen.io/aaroniker/pen/MWgNKGr
Silakan dimodifikasi, disesuaikan, dan dijadikan bagian dari ekosistem digital yang sedang teman bangun.

