1

I am making a vanilla js carousel. I have laid out basic previous and next functionality using js along with html and css.

Now I tried to use css-animations (keyframes) to do left and right slide-in/slide-out animations but the code became messy for me. So here I am asking that what minimal changes would be needed to get the same animation effects in this implementation ?

Will you go for pure JS based or pure CSS based or a mix to do the same ?

My goal is get proper animation with minimal code.

(function () {
  let visibleIndex = 0;
  let carousalImages = document.querySelectorAll(".carousal__image");
  let totalImages = [...carousalImages].length;

  function makeNextVisible() {
    visibleIndex++;
    if (visibleIndex > totalImages - 1) {
      visibleIndex = 0;
    }
    resetVisible();
    renderVisible();
  }
  function makePrevVisible() {
    visibleIndex--;
    if (visibleIndex < 0) {
      visibleIndex = totalImages - 1;
    }
    resetVisible();
    renderVisible();
  }

  function resetVisible() {
    for (let index = 0; index < totalImages; index++) {
      carousalImages[index].className = "carousal__image";
    }
  }
  function renderVisible() {
    carousalImages[visibleIndex].className = "carousal__image--visible";
  }

  function renderCarousel({ autoplay = false, autoplayTime = 1000 } = {}) {
    if (autoplay) {
      [...document.querySelectorAll("button")].forEach(
        (btn) => (btn.style.display = "none")
      );
      setInterval(() => {
        makeNextVisible();
      }, autoplayTime);
    } else renderVisible();
  }

  renderCarousel();

  // Add {autoplay:true} as argument to above to autplay the carousel.

  this.makeNextVisible = makeNextVisible;
  this.makePrevVisible = makePrevVisible;
})();
.carousal {
  display: flex;
  align-items: center;
}
.carousal__wrapper {
  width: 500px;
  height: 400px;
}
.carousal__images {
  display: flex;
  overflow: hidden;
  list-style-type: none;
  padding: 0;
}
.carousal__image--visible {
  position: relative;
}
.carousal__image {
  display: none;
}
<div class='carousal'>
  <div class='carousal__left'>
    <button onclick='makePrevVisible()'>Left</button>
  </div>
  <section class='carousal__wrapper'>
    <ul class='carousal__images'>
      <li class='carousal__image'>
        <img src='https://fastly.syfy.com/sites/syfy/files/styles/1200x680/public/2018/03/dragon-ball-super-goku-ultra-instinct-mastered-01.jpg?offset-x=0&offset-y=0' alt='UI Goku' / width='500' height='400'/>
      </li>
      <li class='carousal__image'>
        <img src='https://www.theburnin.com/wp-content/uploads/2019/01/super-broly-3.png' alt='Broly Legendary'  width='500' height='400'/>
      </li>
      <li class='carousal__image'>
        <img src='https://lh3.googleusercontent.com/proxy/xjEVDYoZy8-CTtPZGsQCq2PW7I-1YM5_S5GPrAdlYL2i4SBoZC-zgtg2r3MqH85BubDZuR3AAW4Gp6Ue-B-T2Z1FkKW99SPHwAce5Q_unUpwtm4' alt='Vegeta Base' width='500' height='400'/>
      </li>
      <li class='carousal__image'>
        <img src='https://am21.mediaite.com/tms/cnt/uploads/2018/09/GohanSS2.jpg' alt='Gohan SS2'  width='500' height='400'/>
      </li>
    </ul>
  </section>
  <div class='carousal__right'>
    <button onclick='makeNextVisible()'>Right</button>
  </div>
</div>

Updated codepen with feedback from the below answers and minor additional functionalities = https://codepen.io/lapstjup/pen/RwoRWVe

Lakshya Thakur
  • 8,030
  • 1
  • 12
  • 39

2 Answers2

2

I think the trick is pretty simple. ;)

You should not move one or two images at the same time. Instead you should move ALL images at once.

Let's start with the CSS:

.carousal {
  position: relative;
  display: block;
}

.carousal__wrapper {
  width: 500px;
  height: 400px;
  position: relative;
  display: block;
  overflow: hidden;
  margin: 0;
  padding: 0;
}
.carousal__wrapper,
.carousal__images {
  transform: translate3d(0, 0, 0);
}

.carousal__images {
  position: relative;
  top: 0;
  left: 0;
  display: block;
  margin-left: auto;
  margin-right: auto;
}

.carousal__image {
  float: left;
  height: 100%;
  min-height: 1px;
}

2nd step would be to calculate the maximum width for .carousal__images. For example in your case 4 * 500px makes 2000px. This value must be added to your carousal__images as part of the style attribute style="width: 2000px".

3rd step would be to calculate the next animation point and using transform: translate3d. We start at 0 and want the next slide which means that we have slide to the left. We also know the width of one slide. So the result would be -500px which also has to be added the style attribute of carousal__images => style="width: 2000px; transform: translate3d(-500px, 0px, 0px);"

That's it.

Link to my CodePen: Codepen for Basic Carousel with Autoplay

Paul Facklam
  • 1,623
  • 12
  • 16
  • Hey Paul looks good. What if we want to cycle through the images ? – Lakshya Thakur Feb 08 '21 at 16:47
  • 1
    You mean a real infinite mode? Then we just have to clone the first and the last items and add it to the carousel. Last item clone at the beginning and the first item clone at the end. Trick is to jump to the first item if you reach it's clone and vice versa. – Paul Facklam Feb 08 '21 at 17:15
1

Try this. First stack all the images next to each other in a div and only show a single image at a time by setting overflow property to hidden for the div. Next, add event listeners to the buttons. When a bottom is clicked, the div containing the images is translated by -{size of an image} * {image number} on the x axis. For smooth animation, add transition: all 0.5s ease-in-out; to the div.

When someone clicks left arrow on the first image, the slide should display the last image. So for that counter is set to {number of images} - 1 and image is translated to left size * counter px.

For every click on the right arrow, the counter is incremented by 1 and slide is moved left. For every click on the left arrow, the counter is decremented by 1.

Slide.style.transform = "translateX(" + (-size * counter) + "px)"; this is the condition which is deciding how much the slide should be translated.

const PreviousButton = document.querySelector(".Previous-Button");
const NextButton = document.querySelector(".Next-Button");
const Images = document.querySelectorAll("img");
const Slide = document.querySelector(".Images");
const size = Slide.clientWidth;
var counter = 0;

// Arrow Click Events
PreviousButton.addEventListener("click", Previous);
NextButton.addEventListener("click", Next);

function Previous() {
  counter--;

  if (counter < 0) {
    counter = Images.length - 1;
  }

  Slide.style.transform = "translateX(" + (-size * counter) + "px)";
}

function Next() {
  counter++;

  if (counter >= Images.length) {
    counter = 0;
  }

  Slide.style.transform = "translateX(" + (-size * counter) + "px)";
}
* {
  margin: 0px;
  padding: 0px;
  box-sizing: border-box;
}

.Container {
  width: 60%;
  margin: 0px auto;
  margin-top: 90px;
  overflow: hidden;
  position: relative;
}

.Container .Images img {
  width: 100%;
}

.Images {
  transition: all 0.5s ease-in-out;
}

.Container .Previous-Button {
  position: absolute;
  background: transparent;
  border: 0px;
  outline: 0px;
  top: 50%;
  left: 20px;
  transform: translateY(-50%);
  filter: invert(80%);
  z-index: 1;
}

.Container .Next-Button {
  position: absolute;
  background: transparent;
  border: 0px;
  outline: 0px;
  top: 50%;
  right: 20px;
  transform: translateY(-50%);
  filter: invert(80%);
  z-index: 1;
}

.Container .Images {
  display: flex;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link href="https://fonts.googleapis.com/css2?family=Cabin&family=Poppins&display=swap" rel="stylesheet">
  <link rel="stylesheet" href="style.css">
  <title>Carousel</title>
</head>

<body>
  <div class="Container">
    <button class="Previous-Button">
            <svg style = "transform: rotate(180deg);" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M8.122 24l-4.122-4 8-8-8-8 4.122-4 11.878 12z"/></svg>
        </button>
    <button class="Next-Button">
            <svg xmlns="http://www.w3.org/2000/svg" width = "24" height = "24" viewBox = "0 0 24 24"><path d="M8.122 24l-4.122-4 8-8-8-8 4.122-4 11.878 12z"/></svg>
        </button>
    <div class="Images">
      <img src="https://source.unsplash.com/1280x720/?nature">
      <img src="https://source.unsplash.com/1280x720/?water">
      <img src="https://source.unsplash.com/1280x720/?rock">
      <img src="https://source.unsplash.com/1280x720/?abstract">
      <img src="https://source.unsplash.com/1280x720/?nature">
      <img src="https://source.unsplash.com/1280x720/?trees">
      <img src="https://source.unsplash.com/1280x720/?human">
      <img src="https://source.unsplash.com/1280x720/?tech">
    </div>
  </div>
  <script src="main.js"></script>
</body>

</html>
Vaibhav
  • 562
  • 5
  • 12