Selamat datang di tutorial pembuatan carousel (slider) kustom! Carousel adalah komponen UI yang populer untuk menampilkan serangkaian konten (gambar, teks, kartu) secara bergantian. Daripada menggunakan library eksternal, membuat carousel sendiri adalah cara terbaik untuk memahami cara kerjanya dan mengasah keterampilan front-end Anda.
Mengapa Membuat Carousel Kustom?
- Pembelajaran Mendalam: Anda akan memahami mekanika di baliknya.
- Kustomisasi Penuh: Anda memiliki kontrol penuh atas tampilan dan perilaku.
- Ringan: Tidak ada kode berlebih dari library yang tidak Anda butuhkan.
Apa yang Akan Kita Buat?
Kita akan membuat carousel gambar sederhana dengan fitur berikut:
- Struktur HTML yang jelas.
- Slide yang bisa berisi gambar atau konten lain.
- Tombol Navigasi “Previous” (Sebelumnya) dan “Next” (Berikutnya).
- Indikator Titik (Dots) untuk menunjukkan slide aktif dan navigasi.
- Transisi Geser (Slide) yang mulus menggunakan CSS.
- Logika JavaScript murni (Vanilla JS) untuk mengontrol fungsionalitas.
- Desain Responsif.
Prasyarat
- Text Editor: VS Code, Sublime Text, dll.
- Web Browser: Chrome, Firefox, dll.
- Pemahaman Dasar HTML dan CSS.
- Sedikit Pengetahuan JavaScript: Variabel, fungsi, event listener, manipulasi DOM.
- Beberapa Gambar: Siapkan 3-5 gambar untuk slide carousel Anda.
Mari kita mulai!
Langkah 1: Persiapan Proyek dan Struktur Folder
- Buat folder utama proyek, misalnya
proyek-carousel-kustom
. - Di dalamnya, buat:
index.html
style.css
script.js
- Folder
images
(tempatkan gambar-gambar Anda di sini, misalnyaslide1.jpg
,slide2.jpg
,slide3.jpg
).
Struktur dasar:
proyek-carousel-kustom/
├── index.html
├── style.css
├── script.js
└── images/
├── slide1.jpg
├── slide2.jpg
└── slide3.jpg
└── (gambar lain...)
Langkah 2: Struktur Dasar HTML (index.html
)
Buka index.html
dan buat kerangka dasar carousel.
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Carousel Kustom - Vanilla JS</title>
<link rel="stylesheet" href="style.css">
<!-- Font Awesome untuk ikon panah (opsional, bisa diganti karakter UTF atau SVG) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<div class="carousel-container">
<div class="carousel-track-container">
<ul class="carousel-track">
<li class="carousel-slide current-slide">
<img src="images/slide1.jpg" alt="Gambar Slide 1">
<!-- <div class="carousel-caption">Judul Slide 1</div> -->
</li>
<li class="carousel-slide">
<img src="images/slide2.jpg" alt="Gambar Slide 2">
<!-- <div class="carousel-caption">Judul Slide 2</div> -->
</li>
<li class="carousel-slide">
<img src="images/slide3.jpg" alt="Gambar Slide 3">
<!-- <div class="carousel-caption">Judul Slide 3</div> -->
</li>
<!-- Tambahkan lebih banyak slide jika perlu -->
</ul>
</div>
<button class="carousel-button carousel-button--left">
<i class="fas fa-chevron-left"></i>
</button>
<button class="carousel-button carousel-button--right">
<i class="fas fa-chevron-right"></i>
</button>
<div class="carousel-nav">
<button class="carousel-indicator current-slide-indicator"></button>
<button class="carousel-indicator"></button>
<button class="carousel-indicator"></button>
<!-- Jumlah indikator harus sama dengan jumlah slide -->
</div>
</div>
<script src="script.js"></script>
</body>
</html>
Penjelasan HTML:
.carousel-container
: Wrapper utama carousel. Ini akan memilikioverflow: hidden
..carousel-track-container
: Kontainer untuk track, membantu dalam beberapa teknik styling. (Dalam kasus sederhana, ini mungkin tidak selalu diperlukan, tapi bisa berguna untuk layout yang lebih kompleks atau efek). Untuk kasus ini, kita bisa menyederhanakannya jika track langsung di dalam container. Kita akan buat tanpacarousel-track-container
agar lebih sederhana..carousel-track
: Ini adalah elemenul
yang akan bergerak secara horizontal. Lebarnya akan jauh lebih besar dari kontainernya..carousel-slide
: Setiapli
adalah satu slide.current-slide
menandai slide yang aktif..carousel-button
: Tombol “Previous” dan “Next”..carousel-nav
: Kontainer untuk indikator titik (dots)..carousel-indicator
: Setiap tombol titik.current-slide-indicator
menandai titik untuk slide yang aktif.
Penyederhanaan Struktur (Opsional): Kita bisa menghilangkan .carousel-track-container
jika tidak ada kebutuhan khusus. Mari kita lanjutkan dengan struktur di atas dulu, dan jika tidak diperlukan, kita bisa hapus di CSS nanti. Untuk tutorial ini, kita akan coba tanpa .carousel-track-container
agar lebih fokus pada .carousel-track
itu sendiri.
Struktur HTML yang disederhanakan (digunakan untuk tutorial ini):
<!-- index.html (bagian carousel) -->
<div class="carousel-container">
<ul class="carousel-track">
<li class="carousel-slide current-slide">
<img src="images/slide1.jpg" alt="Gambar Slide 1">
</li>
<li class="carousel-slide">
<img src="images/slide2.jpg" alt="Gambar Slide 2">
</li>
<li class="carousel-slide">
<img src="images/slide3.jpg" alt="Gambar Slide 3">
</li>
</ul>
<button class="carousel-button carousel-button--left">
<i class="fas fa-chevron-left"></i>
</button>
<button class="carousel-button carousel-button--right">
<i class="fas fa-chevron-right"></i>
</button>
<div class="carousel-nav">
<!-- Indikator akan dibuat dinamis dengan JS atau di-hardcode sejumlah slide -->
</div>
</div>
Kita akan membuat indikator secara dinamis dengan JS nanti, jadi biarkan .carousel-nav
kosong dulu.
Langkah 3: Styling Dasar CSS (style.css
)
Buka style.css
. Kita akan mengatur agar slide berjajar horizontal dan hanya satu yang terlihat.
/* style.css */
body {
margin: 0;
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f0f0f0;
}
.carousel-container {
position: relative; /* Kunci untuk positioning absolut tombol dan nav */
width: 80%; /* Atau ukuran spesifik: 800px */
max-width: 900px;
height: 500px; /* Sesuaikan dengan rasio gambar Anda */
overflow: hidden; /* SANGAT PENTING: menyembunyikan slide lain */
border-radius: 10px;
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
}
.carousel-track {
list-style: none;
padding: 0;
margin: 0;
height: 100%;
display: flex; /* Membuat semua slide berjajar horizontal */
transition: transform 0.5s ease-in-out; /* Animasi geser */
/* Lebar track akan diatur oleh JS atau bisa 300% jika ada 3 slide, 100% per slide */
}
.carousel-slide {
min-width: 100%; /* Setiap slide mengambil lebar penuh dari container */
height: 100%;
/* flex-basis: 100%; juga bisa digunakan */
/* display: flex;
align-items: center;
justify-content: center; */
}
.carousel-slide img {
width: 100%;
height: 100%;
object-fit: cover; /* Memastikan gambar mengisi slide tanpa distorsi */
display: block; /* Menghilangkan spasi ekstra di bawah gambar */
}
/* Caption (Opsional) */
.carousel-caption {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0,0,0,0.5);
color: white;
padding: 10px 20px;
border-radius: 5px;
font-size: 1.2rem;
}
Penjelasan CSS Awal:
.carousel-container
:position: relative
agar tombol navigasi dan dots bisa diposisikan absolut terhadapnya.overflow: hidden
adalah properti ajaib yang membuat hanya satu slide terlihat..carousel-track
:display: flex
membuatli
(slide) berjajar horizontal.transition
akan menganimasikan perubahan propertitransform
(yang akan kita gunakan untuk menggeser track)..carousel-slide
:min-width: 100%
memastikan setiap slide mengambil lebar penuh dari.carousel-container
..carousel-slide img
:object-fit: cover
membuat gambar mengisi area slide dengan baik.
Jika Anda buka index.html
sekarang, Anda akan melihat slide pertama, dan jika Anda inspect element pada .carousel-track
lalu secara manual menambahkan transform: translateX(-100%);
, Anda akan melihat slide kedua. Ini dasar dari cara kerjanya.
Langkah 4: Styling Tombol Navigasi dan Indikator Titik
/* Lanjutan style.css */
.carousel-button {
position: absolute;
top: 50%;
transform: translateY(-50%);
background-color: rgba(0,0,0,0.4);
color: white;
border: none;
padding: 15px; /* Lebih besar agar mudah diklik */
cursor: pointer;
z-index: 10; /* Di atas slide */
border-radius: 50%; /* Membuat tombol bulat */
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
transition: background-color 0.3s ease;
}
.carousel-button:hover {
background-color: rgba(0,0,0,0.7);
}
.carousel-button--left {
left: 20px;
}
.carousel-button--right {
right: 20px;
}
/* Tombol disabled (akan ditambahkan dengan JS) */
.carousel-button.is-hidden {
display: none; /* Atau opacity: 0.5; pointer-events: none; */
}
.carousel-nav {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
z-index: 10;
}
.carousel-indicator {
background-color: rgba(255,255,255,0.5); /* Titik transparan */
border: none;
width: 12px;
height: 12px;
border-radius: 50%;
margin: 0 6px;
cursor: pointer;
padding: 0; /* Hapus padding default tombol */
transition: background-color 0.3s ease, transform 0.3s ease;
}
.carousel-indicator:hover {
background-color: rgba(255,255,255,0.8);
transform: scale(1.1);
}
.carousel-indicator.current-slide-indicator {
background-color: white; /* Titik aktif lebih jelas */
transform: scale(1.2);
}
Langkah 5: JavaScript - Seleksi Elemen dan Inisialisasi State
Buka script.js
. Kita akan mulai dengan mengambil referensi ke elemen HTML yang kita butuhkan.
// script.js
document.addEventListener('DOMContentLoaded', () => {
const track = document.querySelector('.carousel-track');
const slides = Array.from(track.children); // Mengubah HTMLCollection menjadi Array
const nextButton = document.querySelector('.carousel-button--right');
const prevButton = document.querySelector('.carousel-button--left');
const dotsNav = document.querySelector('.carousel-nav');
// Jika ingin membuat dots secara dinamis
slides.forEach((slide, index) => {
const dot = document.createElement('button');
dot.classList.add('carousel-indicator');
if (index === 0) {
dot.classList.add('current-slide-indicator');
}
dotsNav.appendChild(dot);
});
const dots = Array.from(dotsNav.children);
const slideWidth = slides[0].getBoundingClientRect().width; // Lebar satu slide
let currentIndex = 0; // Indeks slide yang sedang aktif
// Atur posisi awal slide (tidak selalu diperlukan jika CSS sudah benar)
// slides.forEach((slide, index) => {
// slide.style.left = slideWidth * index + 'px'; // Ini untuk positioning absolut, kita pakai flexbox jadi tidak perlu
// });
// Fungsi untuk memindahkan slide
const moveToSlide = (targetIndex) => {
if (!track || !slides[targetIndex]) return; // Guard clause
track.style.transform = `translateX(-${slideWidth * targetIndex}px)`;
// Update kelas 'current-slide' pada slide
slides[currentIndex].classList.remove('current-slide');
slides[targetIndex].classList.add('current-slide');
// Update kelas 'current-slide-indicator' pada dot
if (dots.length > 0) {
dots[currentIndex].classList.remove('current-slide-indicator');
dots[targetIndex].classList.add('current-slide-indicator');
}
currentIndex = targetIndex;
updateNavButtons();
};
// Fungsi untuk update status tombol prev/next
const updateNavButtons = () => {
if (!prevButton || !nextButton) return;
if (currentIndex === 0) {
prevButton.classList.add('is-hidden'); // Sembunyikan tombol prev di slide pertama
nextButton.classList.remove('is-hidden');
} else if (currentIndex === slides.length - 1) {
nextButton.classList.add('is-hidden'); // Sembunyikan tombol next di slide terakhir
prevButton.classList.remove('is-hidden');
} else {
prevButton.classList.remove('is-hidden');
nextButton.classList.remove('is-hidden');
}
};
// Panggil updateNavButtons saat pertama kali load
updateNavButtons();
// Event Listener untuk Tombol Next
if (nextButton) {
nextButton.addEventListener('click', e => {
if (currentIndex < slides.length - 1) {
moveToSlide(currentIndex + 1);
}
});
}
// Event Listener untuk Tombol Prev
if (prevButton) {
prevButton.addEventListener('click', e => {
if (currentIndex > 0) {
moveToSlide(currentIndex - 1);
}
});
}
// Event Listener untuk Indikator Titik
if (dotsNav) {
dotsNav.addEventListener('click', e => {
const targetDot = e.target.closest('button.carousel-indicator');
if (!targetDot) return;
const targetIndex = dots.findIndex(dot => dot === targetDot);
if (targetIndex !== -1) {
moveToSlide(targetIndex);
}
});
}
// (Opsional) Responsif: Update slideWidth jika ukuran window berubah
// Ini penting jika lebar carousel Anda responsif (misal, width: 80%)
let resizeTimeout;
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
const newSlideWidth = slides[0].getBoundingClientRect().width;
// Hanya update jika lebar benar-benar berubah untuk menghindari kalkulasi berlebih
if (newSlideWidth !== slideWidth) {
// Perlu cara untuk mendapatkan slideWidth yang baru dan mengaplikasikannya
// Untuk carousel flexbox sederhana ini, width:100% pada slide sudah cukup.
// Yang perlu di-recalculate adalah `transform` jika slideWidth berubah.
// Jadi, panggil moveToSlide lagi dengan index saat ini agar `translateX` dihitung ulang.
// Tapi, karena slideWidth didapat dari `getBoundingClientRect`, ia sudah akan otomatis benar
// saat dipanggil di `moveToSlide`.
// Poin penting adalah: `getBoundingClientRect().width` akan selalu memberikan lebar aktual.
// Jadi, kita hanya perlu memastikan `moveToSlide` menggunakan nilai terbaru.
// Untuk kasus ini, karena `slideWidth` hanya di-assign sekali, kita perlu meng-update-nya.
// Atau, lebih baik, ambil `slideWidth` langsung di dalam `moveToSlide`.
// Versi lebih baik: Dapatkan slideWidth dinamis di dalam moveToSlide
// Maka, hapus `const slideWidth = slides[0].getBoundingClientRect().width;` di atas
// dan letakkan di dalam `moveToSlide`
// Contoh di bawah akan pakai `slideWidth` yang di-scope di luar fungsi dulu.
// Jika ada masalah responsivitas, pindahkan kalkulasi slideWidth ke dalam moveToSlide.
// Cara 1: Update slideWidth global dan panggil moveToSlide
// slideWidth = newSlideWidth; // Update variabel globalnya
// moveToSlide(currentIndex);
// Cara 2 (lebih bersih): Hitung slideWidth di dalam moveToSlide
// (Ini akan jadi pembaruan di langkah berikutnya jika diperlukan)
// Untuk sekarang, kita panggil ulang moveToSlide dengan currentIndex
// Ini akan menggunakan slideWidth yang lama, tapi untuk resize biasanya cukup cepat
// sehingga nilai baru sudah terpakai di `getBoundingClientRect` berikutnya.
// Jika tidak, kita perlu memodifikasi `moveToSlide`.
// *Revisi untuk Responsivitas*
// Kita modifikasi moveToSlide agar selalu menggunakan lebar slide saat ini.
// Hapus deklarasi `const slideWidth = slides[0].getBoundingClientRect().width;` di atas
// dan modifikasi `moveToSlide`
// Untuk sekarang, panggil ulang moveToSlide dengan currentIndex
moveToSlide(currentIndex); // Panggil ulang untuk re-calculate transform
}, 250); // Debounce
});
});
Revisi moveToSlide
untuk Responsivitas yang Lebih Baik:
Hapus const slideWidth = slides[0].getBoundingClientRect().width;
dari bagian atas script.js
.
Kemudian, modifikasi fungsi moveToSlide
menjadi seperti ini:
// script.js (modifikasi fungsi moveToSlide)
// ... (kode di atasnya tetap sama)
// Fungsi untuk memindahkan slide
const moveToSlide = (targetIndex) => {
if (!track || !slides[targetIndex]) return;
const currentSlideWidth = slides[targetIndex].getBoundingClientRect().width; // Dapatkan lebar saat ini
track.style.transform = `translateX(-${currentSlideWidth * targetIndex}px)`;
// Update kelas 'current-slide' pada slide
// Pastikan currentIndex di-update sebelum menghapus kelas dari slide lama
if (slides[currentIndex]) { // Cek jika currentIndex valid
slides[currentIndex].classList.remove('current-slide');
}
slides[targetIndex].classList.add('current-slide');
// Update kelas 'current-slide-indicator' pada dot
if (dots.length > 0 && dots[currentIndex] && dots[targetIndex]) { // Cek dots
dots[currentIndex].classList.remove('current-slide-indicator');
dots[targetIndex].classList.add('current-slide-indicator');
}
currentIndex = targetIndex; // Update currentIndex SETELAH semua operasi yang membutuhkannya
updateNavButtons();
};
// ... (sisa kode tetap sama, termasuk event listener resize yang memanggil moveToSlide(currentIndex))
Penjelasan JavaScript:
- Seleksi Elemen: Mengambil semua elemen DOM yang kita butuhkan.
Array.from(track.children)
mengubah HTMLCollection menjadi array agar kita bisa menggunakan metode array sepertiforEach
ataufindIndex
. - Pembuatan Dots Dinamis: Looping melalui
slides
untuk membuat tombol indikator sejumlah slide. slideWidth
: (Sebelum revisi) Mendapatkan lebar satu slide. Ini penting untuk kalkulasitranslateX
. (Setelah revisi) Dihitung dinamis di dalammoveToSlide
.currentIndex
: Menyimpan indeks slide yang sedang aktif (dimulai dari 0).moveToSlide(targetIndex)
: Fungsi inti.- Menghitung seberapa jauh
.carousel-track
harus digeser ke kiri (translateX
). - Mengupdate kelas
current-slide
pada elemen slide. - Mengupdate kelas
current-slide-indicator
pada elemen dot. - Memperbarui
currentIndex
. - Memanggil
updateNavButtons()
untuk menyembunyikan/menampilkan tombol prev/next.
- Menghitung seberapa jauh
updateNavButtons()
: Menyembunyikan tombol “prev” jika di slide pertama, dan tombol “next” jika di slide terakhir.- Event Listeners:
- Untuk tombol “Next”: Pindah ke
currentIndex + 1
. - Untuk tombol “Prev”: Pindah ke
currentIndex - 1
. - Untuk Dots: Mendapatkan indeks dot yang diklik, lalu pindah ke slide dengan indeks tersebut.
- Untuk tombol “Next”: Pindah ke
- Resize Listener (Opsional tapi Penting untuk Responsif):
- Saat ukuran window berubah, lebar slide (
slideWidth
) bisa berubah jika carousel Anda menggunakan unit persentase. getBoundingClientRect().width
akan selalu memberikan lebar aktual elemen.- Dengan memanggil
moveToSlide(currentIndex)
lagi setelah resize (dengan debounce), posisitranslateX
akan dihitung ulang menggunakanslideWidth
yang baru, menjaga carousel tetap sinkron. Revisi terakhir memastikancurrentSlideWidth
selalu yang terbaru.
- Saat ukuran window berubah, lebar slide (
Langkah 6: Menguji Carousel Anda
Simpan semua file (index.html
, style.css
, script.js
). Buka index.html
di browser Anda. Anda seharusnya sekarang memiliki carousel yang berfungsi!
- Tombol Previous/Next seharusnya bekerja.
- Indikator titik seharusnya bekerja dan menunjukkan slide aktif.
- Transisi geser seharusnya mulus.
- Tombol prev/next seharusnya disembunyikan di slide pertama/terakhir.
- Coba resize window browser Anda, carousel seharusnya menyesuaikan diri.
Langkah 7: (Opsional) Auto-Play
Jika Anda ingin carousel berputar otomatis:
Tambahkan ini di dalam script.js
Anda, di dalam DOMContentLoaded
:
// script.js (lanjutan, di dalam DOMContentLoaded)
// ... (semua kode sebelumnya) ...
// --- Fitur Auto-Play (Opsional) ---
let autoPlayInterval = null;
const autoPlayDelay = 5000; // 5 detik
function startAutoPlay() {
// Mencegah beberapa interval berjalan bersamaan
if (autoPlayInterval) clearInterval(autoPlayInterval);
autoPlayInterval = setInterval(() => {
// Menggunakan operator modulo untuk looping kembali ke awal
const nextIndex = (currentIndex + 1) % slides.length;
moveToSlide(nextIndex);
}, autoPlayDelay);
}
function stopAutoPlay() {
clearInterval(autoPlayInterval);
}
// Mulai auto-play saat halaman dimuat
startAutoPlay();
// Jeda auto-play saat mouse berada di atas carousel (user-friendly)
const carouselContainer = document.querySelector('.carousel-container');
if (carouselContainer) {
carouselContainer.addEventListener('mouseenter', stopAutoPlay);
carouselContainer.addEventListener('mouseleave', startAutoPlay);
}
Sekarang, kita perlu sedikit memodifikasi event listener yang ada agar auto-play di-reset setiap kali pengguna berinteraksi secara manual. Ini memberikan pengalaman yang lebih baik, karena timer akan diatur ulang setelah pengguna mengklik.
Modifikasi Event Listener yang Sudah Ada:
Ganti blok event listener yang lama dengan yang baru di bawah ini. Kita hanya menambahkan panggilan ke stopAutoPlay()
dan startAutoPlay()
.
// script.js (modifikasi event listener)
// Event Listener untuk Tombol Next
if (nextButton) {
nextButton.addEventListener('click', e => {
if (currentIndex < slides.length - 1) {
moveToSlide(currentIndex + 1);
} else {
// Opsional: jika ingin looping saat klik tombol next di slide terakhir
// moveToSlide(0);
}
// Reset auto-play timer setelah interaksi manual
// startAutoPlay(); // Jika ingin auto-play lanjut setelah klik
});
}
// Event Listener untuk Tombol Prev
if (prevButton) {
prevButton.addEventListener('click', e => {
if (currentIndex > 0) {
moveToSlide(currentIndex - 1);
} else {
// Opsional: jika ingin looping saat klik tombol prev di slide pertama
// moveToSlide(slides.length - 1);
}
// Reset auto-play timer setelah interaksi manual
// startAutoPlay(); // Jika ingin auto-play lanjut setelah klik
});
}
// Event Listener untuk Indikator Titik
if (dotsNav) {
dotsNav.addEventListener('click', e => {
const targetDot = e.target.closest('button.carousel-indicator');
if (!targetDot) return;
const targetIndex = dots.findIndex(dot => dot === targetDot);
if (targetIndex !== -1) {
moveToSlide(targetIndex);
// Reset auto-play timer setelah interaksi manual
// startAutoPlay(); // Jika ingin auto-play lanjut setelah klik
}
});
}
Catatan: Saya menonaktifkan startAutoPlay()
di dalam klik dengan komentar. Perilaku umum adalah jika pengguna berinteraksi manual, auto-play berhenti. Jika Anda ingin auto-play dilanjutkan setelah klik, hapus tanda komentar //
.
Langkah 8: Penyempurnaan Lanjutan (Opsional)
Carousel Anda sudah berfungsi penuh. Namun, ada banyak cara untuk membuatnya lebih canggih.
- Looping Tak Terbatas (Infinite Loop): Saat ini, carousel berhenti di slide pertama dan terakhir. Untuk membuat loop tak terbatas, Anda perlu teknik yang lebih canggih, biasanya dengan menduplikasi slide pertama dan terakhir, lalu memindahkannya secara instan tanpa transisi saat mencapai ujung “palsu” tersebut.
- Gestur Geser (Swipe/Drag) untuk Mobile: Untuk pengalaman mobile yang lebih baik, tambahkan event listener untuk
touchstart
,touchmove
, dantouchend
untuk memungkinkan pengguna menggeser slide dengan jari. - Aksesibilitas (WAI-ARIA): Tingkatkan aksesibilitas untuk pengguna pembaca layar dengan menambahkan atribut ARIA seperti
aria-live
pada track,aria-controls
pada tombol, danaria-label
untuk navigasi yang lebih jelas. - Transisi Fade: Jika Anda tidak suka efek geser, Anda bisa mengubahnya menjadi efek fade. Ini memerlukan perubahan CSS (menggunakan
opacity
danposition: absolute
pada slide) dan sedikit logika JavaScript yang berbeda. - Lazy Loading Gambar: Jika carousel Anda memiliki banyak gambar beresolusi tinggi, memuat semuanya sekaligus dapat memperlambat situs. Terapkan lazy loading agar gambar hanya dimuat saat slide akan ditampilkan.
Penutup
Selamat! Anda telah berhasil membangun carousel kustom dari awal hanya dengan HTML, CSS, dan JavaScript murni. Anda sekarang memiliki pemahaman yang kuat tentang bagaimana komponen populer ini bekerja di balik layar. Anda telah belajar cara:
- Menyusun HTML semantik untuk carousel.
- Menggunakan CSS Flexbox dan
transform
untuk membuat efek geser. - Menggunakan
overflow: hidden
untuk menampilkan satu slide pada satu waktu. - Menulis logika JavaScript untuk mengontrol navigasi, memperbarui state, dan menangani interaksi pengguna.
- Membuat komponen yang responsif dan menambahkan fitur opsional seperti auto-play.
Proyek ini adalah fondasi yang bagus. Jangan ragu untuk bereksperimen dengan ide-ide dari bagian “Penyempurnaan Lanjutan” untuk terus mengasah keterampilan Anda!
Kode Lengkap
Untuk referensi, berikut adalah kode lengkap untuk setiap file.
index.html
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Carousel Kustom - Vanilla JS</title>
<link rel="stylesheet" href="style.css">
<!-- Font Awesome untuk ikon panah -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
</head>
<body>
<div class="carousel-container">
<ul class="carousel-track">
<li class="carousel-slide current-slide">
<img src="images/slide1.jpg" alt="Gambar Slide 1">
</li>
<li class="carousel-slide">
<img src="images/slide2.jpg" alt="Gambar Slide 2">
</li>
<li class="carousel-slide">
<img src="images/slide3.jpg" alt="Gambar Slide 3">
</li>
<li class="carousel-slide">
<img src="images/slide4.jpg" alt="Gambar Slide 4">
</li>
</ul>
<button class="carousel-button carousel-button--left">
<i class="fas fa-chevron-left"></i>
</button>
<button class="carousel-button carousel-button--right">
<i class="fas fa-chevron-right"></i>
</button>
<div class="carousel-nav">
<!-- Indikator titik akan dibuat oleh JavaScript -->
</div>
</div>
<script src="script.js"></script>
</body>
</html>
style.css
/* style.css */
body {
margin: 0;
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background-color: #f0f0f0;
}
.carousel-container {
position: relative;
width: 80%;
max-width: 900px;
height: 500px;
overflow: hidden;
border-radius: 10px;
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
}
.carousel-track {
list-style: none;
padding: 0;
margin: 0;
height: 100%;
display: flex;
transition: transform 0.5s ease-in-out;
}
.carousel-slide {
min-width: 100%;
height: 100%;
}
.carousel-slide img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.carousel-button {
position: absolute;
top: 50%;
transform: translateY(-50%);
background-color: rgba(0,0,0,0.4);
color: white;
border: none;
padding: 15px;
cursor: pointer;
z-index: 10;
border-radius: 50%;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.2rem;
transition: background-color 0.3s ease;
}
.carousel-button:hover {
background-color: rgba(0,0,0,0.7);
}
.carousel-button--left {
left: 20px;
}
.carousel-button--right {
right: 20px;
}
.carousel-button.is-hidden {
display: none;
}
.carousel-nav {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
z-index: 10;
}
.carousel-indicator {
background-color: rgba(255,255,255,0.5);
border: none;
width: 12px;
height: 12px;
border-radius: 50%;
margin: 0 6px;
cursor: pointer;
padding: 0;
transition: background-color 0.3s ease, transform 0.3s ease;
}
.carousel-indicator:hover {
background-color: rgba(255,255,255,0.8);
transform: scale(1.1);
}
.carousel-indicator.current-slide-indicator {
background-color: white;
transform: scale(1.2);
}
script.js
// script.js
document.addEventListener('DOMContentLoaded', () => {
const track = document.querySelector('.carousel-track');
const slides = Array.from(track.children);
const nextButton = document.querySelector('.carousel-button--right');
const prevButton = document.querySelector('.carousel-button--left');
const dotsNav = document.querySelector('.carousel-nav');
const carouselContainer = document.querySelector('.carousel-container');
// Buat indikator titik secara dinamis
slides.forEach((slide, index) => {
const dot = document.createElement('button');
dot.classList.add('carousel-indicator');
if (index === 0) {
dot.classList.add('current-slide-indicator');
}
dotsNav.appendChild(dot);
});
const dots = Array.from(dotsNav.children);
let currentIndex = 0;
const moveToSlide = (targetIndex) => {
if (!track || !slides[targetIndex]) return;
const currentSlideWidth = slides[targetIndex].getBoundingClientRect().width;
track.style.transform = `translateX(-${currentSlideWidth * targetIndex}px)`;
slides[currentIndex].classList.remove('current-slide');
slides[targetIndex].classList.add('current-slide');
dots[currentIndex].classList.remove('current-slide-indicator');
dots[targetIndex].classList.add('current-slide-indicator');
currentIndex = targetIndex;
updateNavButtons();
};
const updateNavButtons = () => {
if (currentIndex === 0) {
prevButton.classList.add('is-hidden');
nextButton.classList.remove('is-hidden');
} else if (currentIndex === slides.length - 1) {
nextButton.classList.add('is-hidden');
prevButton.classList.remove('is-hidden');
} else {
prevButton.classList.remove('is-hidden');
nextButton.classList.remove('is-hidden');
}
};
updateNavButtons();
nextButton.addEventListener('click', e => {
if (currentIndex < slides.length - 1) {
moveToSlide(currentIndex + 1);
}
});
prevButton.addEventListener('click', e => {
if (currentIndex > 0) {
moveToSlide(currentIndex - 1);
}
});
dotsNav.addEventListener('click', e => {
const targetDot = e.target.closest('button.carousel-indicator');
if (!targetDot) return;
const targetIndex = dots.findIndex(dot => dot === targetDot);
if (targetIndex !== -1) {
moveToSlide(targetIndex);
}
});
let resizeTimeout;
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
moveToSlide(currentIndex);
}, 250);
});
// --- Fitur Auto-Play (Opsional) ---
let autoPlayInterval = null;
const autoPlayDelay = 4000; // 4 detik
function startAutoPlay() {
if (autoPlayInterval) clearInterval(autoPlayInterval);
autoPlayInterval = setInterval(() => {
const nextIndex = (currentIndex + 1) % slides.length;
moveToSlide(nextIndex);
}, autoPlayDelay);
}
function stopAutoPlay() {
clearInterval(autoPlayInterval);
}
// Perilaku auto-play:
// 1. Jeda saat mouse di atas carousel
carouselContainer.addEventListener('mouseenter', stopAutoPlay);
// 2. Lanjutkan saat mouse meninggalkan carousel
carouselContainer.addEventListener('mouseleave', startAutoPlay);
// 3. Hentikan permanen saat pengguna klik tombol navigasi manual (opsional)
// Untuk ini, tambahkan stopAutoPlay() di dalam event listener tombol.
// Contoh: nextButton.addEventListener('click', e => { stopAutoPlay(); ... });
// Mulai auto-play saat halaman dimuat
startAutoPlay();
});
atau anda dapat temukan di repositori GitHub berikut: Source code lengkap