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:
- use classes to move and show content
- 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/
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">↑ Prev</button>
<button id="next">↓ Next</button>
</div>