Slider Component - JavaScript & CSS
Published: 2024-07-14 07:42:05

Pada kesempatan kali ini kita akan praktik membuat slider, slider bisa berisi image maupun text nantinya disesuaikan dengan kebutuhan. Idenya adalah dengan menyiapkan image maupun text yang akan dijadikan slide, kemudian terdapat button sebagai navigasi kiri-kanan, nantinya kita juga bisa menggunakan arrow keyboard maupun dot. Untuk praktik kali ini kita akan memakai image sebagai content dari slide. Langsung saja kita siapkan file index.html, style.css dan app.js. Untuk file app.js kita akan buat secara bertahap

```html
<!-- index.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="style.css" />
    <title>Slider</title>
  </head>
  <body>
    <div class="slider">
      <div class="slide"><img src="../img/img-1.jpg" alt="Photo 1" /></div>
      <div class="slide"><img src="../img/img-2.jpg" alt="Photo 2" /></div>
      <div class="slide"><img src="../img/img-3.jpg" alt="Photo 3" /></div>
      <div class="slide"><img src="../img/img-4.jpg" alt="Photo 4" /></div>
      <button class="slider__btn slider__btn--left">&larr;</button>
      <button class="slider__btn slider__btn--right">&rarr;</button>
      <div class="dots"></div>
    </div>
    <script src="app.js"></script>
  </body>
</html>
```

```css
/* style.css */

.slider {
  max-width: 100rem;
  height: 50rem;
  margin: 0 auto;
  position: relative;
  overflow: hidden;
}
.slide {
  position: absolute;
  top: 0;
  width: 100%;
  height: 50rem;

  display: flex;
  align-items: center;
  justify-content: center;

  /* untuk animasi */
  transition: transform 1s;
}

.slide > img {
  /* untuk menyamakan ukuran image */
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.slider__btn {
  position: absolute;
  top: 50%;
  z-index: 10;

  border: none;
  background: rgba(255, 255, 255, 0.7);
  font-family: inherit;
  color: #333;
  border-radius: 50%;
  height: 5.5rem;
  width: 5.5rem;
  font-size: 3.25rem;
  cursor: pointer;
}

.slider__btn--left {
  left: 6%;
  transform: translate(-50%, -50%);
}

.slider__btn--right {
  right: 6%;
  transform: translate(50%, -50%);
}

.dots {
  position: absolute;
  bottom: 5%;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
}

.dots__dot {
  border: none;
  background-color: #b9b9b9;
  opacity: 0.7;
  height: 1rem;
  width: 1rem;
  border-radius: 50%;
  margin-right: 1.75rem;
  cursor: pointer;
  transition: all 0.5s;
}

.dots__dot:last-child {
  margin: 0;
}

.dots__dot--active {
  background-color: red;
  opacity: 1;
}
```
Untuk file app.js kita akan buat bertahap sebagai berikut :

Posisikan image pada window view (viewport width) berdampingan, dan geser ketika ada trigger

```javascript
const slides = document.querySelectorAll('.slide');

slides.forEach((s, i) => (s.style.transform = `translateX(${100 * i}%)`));
```
Dengan menggunakan index dari forEach dan translateX kita bisa menentukan posisi viewport setiap kali ada trigger

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8cmsuo31vlx0cogja4wz.png)

Supaya image tampak semua kita kecilkan ukurannya, nantinya kita kembalikan ke ukuran normal jika aplikasi sudah jadi

```javascript
const slides = document.querySelectorAll('.slide');

const slider = document.querySelector('.slider');
slider.style.transform = 'scale(0.2)';
slider.style.overflow = 'visible';

slides.forEach((s, i) => (s.style.transform = `translateX(${100 * i}%)`));
```
Hasilnya sebagai berikut

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/isux9glxy14kismp5yn1.png)

Assign event handler kepada button

```javascript

// Component initiation
const slides = document.querySelectorAll('.slide');
const btnLeft = document.querySelector('.slider__btn--left');
const btnRight = document.querySelector('.slider__btn--right');

let curSlide = 0;

const slider = document.querySelector('.slider');
slider.style.transform = 'scale(0.2)';
slider.style.overflow = 'visible';

slides.forEach((s, i) => (s.style.transform = `translateX(${100 * i}%)`));

btnRight.addEventListener('click', function () {
  curSlide++;
  slides.forEach((s, i) => (s.style.transform = `translateX(${100 * (i - curSlide)}%)`));
});

```

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i1a85qmk9er8tdoe3ie5.gif)

Kita perlu memberi limiter supaya slider tidak bergeser terus setelah image-nya habis. Ketika sudah sampai pada image terakhir kita kembalikan ke image awal. Dengan sedikit refactoring kita bisa menyelesaikan kode untuk app.js sebagai berikut

```javascript
const slides = document.querySelectorAll('.slide');
const btnLeft = document.querySelector('.slider__btn--left');
const btnRight = document.querySelector('.slider__btn--right');

let curSlide = 0;
const maxSlide = slides.length;

const goToSlide = function (slide) {
  slides.forEach((s, i) => (s.style.transform = `translateX(${100 * (i - slide)}%)`));
};

// Activate on first load
goToSlide(0);

// Next slide
const nextSlide = function () {
  if (curSlide === maxSlide - 1) {
    curSlide = 0;
  } else {
    curSlide++;
  }
  goToSlide(curSlide);
};

// Prev slide
const prevSlide = function () {
  if (curSlide === 0) {
    curSlide = maxSlide - 1;
  } else {
    curSlide--;
  }
  goToSlide(curSlide);
};

btnRight.addEventListener('click', nextSlide);
btnLeft.addEventListener('click', prevSlide);
```
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o7dti16apw0x9jjhl0nw.gif)

Selanjutnya kita tambahkan event handler untuk keyboard arrow left dan right

```javascript
...
// Event handler for keyboard arrow
document.addEventListener('keydown', function (e) {
  if (e.key === 'ArrowLeft') prevSlide();
  e.key === 'ArrowRight' && nextSlide();
});
```

Selanjutnya kita tambahkan dot navigation. Kode untuk dot navigation kita tempatkan di awal setelah inisiasi component

```javascript
const dotContainer = document.querySelector('.dots');

// Dot handler
const createDots = function () {
  slides.forEach((_, i) => {
    dotContainer.insertAdjacentHTML(
      'beforeend',
      `<button class="dots__dot" data-slide="${i}"></button>`
    );
  });
};

// Activate on first load
createDots();

// Event handler for dots
dotContainer.addEventListener('click', function (e) {
  if (e.target.classList.contains('dots__dot')) {
    const { slide } = e.target.dataset;
    goToSlide(slide);
  }
});
```

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sir7bi6u4fr5rgh7dag0.gif)

Tahap akhir kita akan tambahkan active navigation, jadi image yang tampak di window juga akan memberi display aktif pada dot navigation. Dalam hal ini dot navigation akan kita beri warna merah

```javascript
// Activate dots
const activateDot = function (slide) {
  // Remove class from all
  document
    .querySelectorAll('.dots__dot')
    .forEach((dot) => dot.classList.remove('dots__dot--active'));

  // Activate class only for selected
  document
    .querySelector(`.dots__dot[data-slide="${slide}"]`)
    .classList.add('dots__dot--active');
};
// Activate on first load
activateDot(0);
```
Kita juga harus menambahkan function activateDot() pada setiap posisi image. Kode lengkap dari app.js adalah sebagai berikut

```javascript
const slides = document.querySelectorAll('.slide');
const btnLeft = document.querySelector('.slider__btn--left');
const btnRight = document.querySelector('.slider__btn--right');
const dotContainer = document.querySelector('.dots');

let curSlide = 0;
const maxSlide = slides.length;

// Dot handler
const createDots = function () {
  slides.forEach((_, i) => {
    dotContainer.insertAdjacentHTML(
      'beforeend',
      `<button class="dots__dot" data-slide="${i}"></button>`
    );
  });
};

// Activate on first load
createDots();

// Activate dots
const activateDot = function (slide) {
  // Remove class from all
  document
    .querySelectorAll('.dots__dot')
    .forEach((dot) => dot.classList.remove('dots__dot--active'));

  // Activate class only for selected
  document
    .querySelector(`.dots__dot[data-slide="${slide}"]`)
    .classList.add('dots__dot--active');
};
// Activate on first load
activateDot(0);

const goToSlide = function (slide) {
  slides.forEach(
    (s, i) => (s.style.transform = `translateX(${100 * (i - slide)}%)`)
  );
};

goToSlide(0);
activateDot(0);

// Next slide
const nextSlide = function () {
  if (curSlide === maxSlide - 1) {
    curSlide = 0;
  } else {
    curSlide++;
  }
  goToSlide(curSlide);
  activateDot(curSlide);
};

const prevSlide = function () {
  if (curSlide === 0) {
    curSlide = maxSlide - 1;
  } else {
    curSlide--;
  }
  goToSlide(curSlide);
  activateDot(curSlide);
};

btnRight.addEventListener('click', nextSlide);
btnLeft.addEventListener('click', prevSlide);

// Event handler for keyboard arrow
document.addEventListener('keydown', function (e) {
  if (e.key === 'ArrowLeft') prevSlide();
  e.key === 'ArrowRight' && nextSlide();
});

// Event handler for dots
dotContainer.addEventListener('click', function (e) {
  if (e.target.classList.contains('dots__dot')) {
    const { slide } = e.target.dataset;
    goToSlide(slide);
    activateDot(slide);
  }
});

```
Hasilnya sebagai berikut

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/brgw8w71290u8anhxcph.gif)