How to create a vertical carousel using plain JavaScript and CSS

16,907

An alternative to using CSS transform properties is to give the carousel absolute positioning inside a wrapper div and manipulate the carousel's top property. Then you can use any easing function you like to animate the sliding motion. In the snippet below, I use cubic easing in/out.

A tricky thing to watch out for is the order in which you rotate the images and perform the sliding animation. When you want to show the next picture below, you have to:

  • slide the carousel up by the height of one picture frame
  • rotate the first image to the end
  • reset the carousel's vertical offset to zero

To show the next picture above:

  • rotate the last image to the beginning
  • instantly move the carousel up by the height of one picture frame
  • slide the carousel down until its vertical offset reaches zero

In the following snippet, you can set the width of the carousel by adjusting Carousel.width at the top of the script. (Although the image height doesn't have to be the same as the image width, I do assume that all images have the same dimensions.) You can also play around with the Carousel.numVisible and Carousel.duration parameters.

var Carousel = {
  width: 100,     // Images are forced into a width of this many pixels.
  numVisible: 2,  // The number of images visible at once.
  duration: 600,  // Animation duration in milliseconds.
  padding: 2      // Vertical padding around each image, in pixels.
};

function rotateForward() {
  var carousel = Carousel.carousel,
      children = carousel.children,
      firstChild = children[0],
      lastChild = children[children.length - 1];
  carousel.insertBefore(lastChild, firstChild);
}
function rotateBackward() {
  var carousel = Carousel.carousel,
      children = carousel.children,
      firstChild = children[0],
      lastChild = children[children.length - 1];
  carousel.insertBefore(firstChild, lastChild.nextSibling);
}

function animate(begin, end, finalTask) {
  var wrapper = Carousel.wrapper,
      carousel = Carousel.carousel,
      change = end - begin,
      duration = Carousel.duration,
      startTime = Date.now();
  carousel.style.top = begin + 'px';
  var animateInterval = window.setInterval(function () {
    var t = Date.now() - startTime;
    if (t >= duration) {
      window.clearInterval(animateInterval);
      finalTask();
      return;
    }
    t /= (duration / 2);
    var top = begin + (t < 1 ? change / 2 * Math.pow(t, 3) :
                               change / 2 * (Math.pow(t - 2, 3) + 2));
    carousel.style.top = top + 'px';
  }, 1000 / 60);
}

window.onload = function () {
  document.getElementById('spinner').style.display = 'none';
  var carousel = Carousel.carousel = document.getElementById('carousel'),
      images = carousel.getElementsByTagName('img'),
      numImages = images.length,
      imageWidth = Carousel.width,
      aspectRatio = images[0].width / images[0].height,
      imageHeight = imageWidth / aspectRatio,
      padding = Carousel.padding,
      rowHeight = Carousel.rowHeight = imageHeight + 2 * padding;
  carousel.style.width = imageWidth + 'px';
  for (var i = 0; i < numImages; ++i) {
    var image = images[i],
        frame = document.createElement('div');
    frame.className = 'pictureFrame';
    var aspectRatio = image.offsetWidth / image.offsetHeight;
    image.style.width = frame.style.width = imageWidth + 'px';
    image.style.height = imageHeight + 'px';
    image.style.paddingTop = padding + 'px';
    image.style.paddingBottom = padding + 'px';
    frame.style.height = rowHeight + 'px';
    carousel.insertBefore(frame, image);
    frame.appendChild(image);
  }
  Carousel.rowHeight = carousel.getElementsByTagName('div')[0].offsetHeight;
  carousel.style.height = Carousel.numVisible * Carousel.rowHeight + 'px';
  carousel.style.visibility = 'visible';
  var wrapper = Carousel.wrapper = document.createElement('div');
  wrapper.id = 'carouselWrapper';
  wrapper.style.width = carousel.offsetWidth + 'px';
  wrapper.style.height = carousel.offsetHeight + 'px';
  carousel.parentNode.insertBefore(wrapper, carousel);
  wrapper.appendChild(carousel);
  var prevButton = document.getElementById('prev'),
      nextButton = document.getElementById('next');
  prevButton.onclick = function () {
    prevButton.disabled = nextButton.disabled = true;
    rotateForward();
    animate(-Carousel.rowHeight, 0, function () {
      carousel.style.top = '0';
      prevButton.disabled = nextButton.disabled = false;
    });
  };
  nextButton.onclick = function () {
    prevButton.disabled = nextButton.disabled = true;
    animate(0, -Carousel.rowHeight, function () {
      rotateBackward();
      carousel.style.top = '0';
      prevButton.disabled = nextButton.disabled = false;
    });
  };
};
body {
  font-family: sans-serif;
}
.buttons {
  margin: 5px 0;
}
button {
  font-size: 14px;
  display: inline;
  padding: 3px 6px;
  border: 2px solid #ccc;
  background: #fff;
  border-radius: 5px;
  outline: none;
}
button:hover {
  border: 2px solid #888;
  background: #ffe;
  cursor: pointer;
}
#carouselWrapper {
  position: relative;
  overflow: hidden;
}
#carousel {
  position: absolute;
  visibility: hidden;
}
<div id="spinner"> 
  Loading...
</div>

<div id="carousel">
  <img src="http://malsup.github.io/images/beach1.jpg">
  <img src="http://malsup.github.io/images/beach2.jpg">
  <img src="http://malsup.github.io/images/beach3.jpg">
  <img src="http://malsup.github.io/images/beach4.jpg">
  <img src="http://malsup.github.io/images/beach5.jpg">
  <img src="http://malsup.github.io/images/beach9.jpg">
</div>

<div class="buttons">
  <button id="prev">&uarr; Prev</button>
  <button id="next">&darr; Next</button>
</div>
Share:
16,907
Dabbler00
Author by

Dabbler00

Updated on June 14, 2022

Comments

  • Dabbler00
    Dabbler00 almost 2 years

    I am trying to create a vertical carousel using vanilla JavaScript and CSS. I know that jQuery has a carousel library but I want to have a go at building this from scratch using no external libraries. I started off by just trying to move the top image and then I planned to move on to making the next image move. I got stuck on the first image. This is where I need your help, StackOverflowers.

    My HTML:

    <div class="slider vertical" >
        <img class="first opened" src="http://malsup.github.io/images/beach1.jpg">
        <img class="opened" src="http://malsup.github.io/images/beach2.jpg">
        <img src="http://malsup.github.io/images/beach3.jpg">
        <img src="http://malsup.github.io/images/beach4.jpg">
        <img src="http://malsup.github.io/images/beach5.jpg">
        <img src="http://malsup.github.io/images/beach9.jpg">
    </div>
    <div class="center">
        <button id="prev">∧ Prev</button>
        <button id="next">∨ Next</button>
    </div>
    

    JavaScript:

    var next = document.getElementById('next');
    var target = document.querySelector('.first');
    
    next.addEventListener('click', nextImg, false);
    
    function nextImg(){
         if (target.classList.contains('opened')) {
            target.classList.remove('opened');
            target.classList.add('closed');
        } else {
            target.classList.remove('closed');
            target.classList.add('opened');
        }
    }
    

    CSS:

    div.vertical {
        width: 100px;
    }
    
    .slider {
        position: relative;
        overflow: hidden;
        height: 250px;
    
            -webkit-box-sizing:border-box;
           -moz-box-sizing:border-box;
            -ms-box-sizing:border-box;
                box-sizing:border-box;
    
        -webkit-transition:-webkit-transform 1.3s ease;
           -moz-transition:   -moz-transform 1.3s ease;
            -ms-transition:    -ms-transform 1.3s ease;
                transition:        transform 1.3s ease;
    }
    
    .slider img {
        width: 100px;
        height: auto;
        padding: 2px;
    }
    
    .first.closed{
        /* partially offscreen */
        -webkit-transform: translate(0, -80%);
           -moz-transform: translate(0, -80%);
            -ms-transform: translate(0, -80%);
                transform: translate(0, -80%);
    }
    
    .first.opened{
        /* visible */
        -webkit-transform: translate(0, 0%);
           -moz-transform: translate(0, 0%);
            -ms-transform: translate(0, 0%);
                transform: translate(0, 0%);
    }
    

    My mode of thinking was:

    1. use classes to move and show content
    2. use JavaScript to add and remove classes

    I think I may not have broken the problem down properly.

    This is how I would like it to look: http://jsfiddle.net/natnaydenova/7uXPx/

    And this is my abysmal attempt: http://jsfiddle.net/6cb58pkr/