Hi Guys Good Day!
Today we're gonna make a Carousel using Vanilla JavaScript. Most of my posts we're about fundamentals and concepts and I've realized I haven't made a post about Implementing stuff or something like that. So today I'm gonna change that. So I've decided to make this post because as of now I'm currently finding a new job and one of the companies that I've applied to has an exam. And in that exam, one of the functionalities that I have to make is an Image Carousel and one of the rules of the exam is that I have to implement it without using existing javascript carousel libraries instead I have to choose one of these technologies that I'm gonna use for the Carousel. React, jQuery, Angular and Vue. I choose React because I love working with React and I want to finish the exam fast. So now, I want to implement it using with just JavaScript. Let's get to the coding stuff.
Our images taken from pexels.com.
Our index.html file.
<!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">
<title>Carousel using Vanilla JS</title>
<link href="./styles.css">
</head>
<body>
<div class="carousel">
<div class="arrow-left">
<span class="arrow">‹</span>
</div>
<img src="./1.jpeg" alt="Carousel Image">
<div class="arrow-right">
<span class="arrow">›</span>
</div>
<div class="indicators">
<span class="active"></span>
<span></span>
<span></span>
</div>
</div>
<script type="text/javascript" src="./carousel.js"></script>
</body>
</html>
This the primary structure of our html. All the elements that we use are inside the div
element with a class of carousel.
Our styles.css file.
.arrow {
font-size: 51px;
font-weight: bold;
border-radius: 50%;
width: 50px;
height: 65px;
color: white;
text-align: center;
display: inline-block;
transition: all 0.3s ease;
}
.arrow:hover {
color: #121212;
background: white;
cursor: pointer;
}
.carousel {
position: relative;
display: block;
width: 600px;
margin: 0 auto;
margin-top: 5%;
}
.arrow-left {
position: absolute;
left: 0;
top: 50%;
margin-left: 5px;
transform: translateY(-50%);
}
.arrow-right {
position: absolute;
right: 0;
top: 50%;
margin-right: 5px;
transform: translateY(-50%);
}
.carousel>img {
width: 100%;
height: 450px;
border-radius: 4px;
}
.d-none {
display: none;
}
.indicators {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 0;
margin-bottom: 10px;
}
.indicators>span {
display: inline-block;
border-radius: 50%;
width: 20px;
height: 20px;
background: white;
}
.indicators>span.active {
background: #4fc355;
}
I'm not pretty good with my css skills (sorry), so bear with me our carousel div
element has a position: relative;
style, so that we can position our arrows and indicators in the correct positions using the position: absolute
inside the our carousel container.
So let's take a look at our carousel.js file. So, I'm gonna divide our js file into multiple sections so I can explain it clearly.
First Section
const arrowLeft = document.querySelector('.arrow-left');
const arrowRight = document.querySelector('.arrow-right');
const imgCarousel = document.querySelector('img');
const indicators = document.querySelectorAll('.indicators > span');
const images = ['./1.jpeg', './2.jpeg', './3.jpeg'];
The first part is declaring and selecting the elements that we gonna use in our
carousel. I use the document.querySelector
and document.querySelectorAll
method in the document
object because it's more flexible than the other method in the document object for
selecting elements. And our images
variable which holds the relative paths of our images, assuming we have the same files. You can change these filenames depending in your files.
Second Section
const setAttr = (el, attr, value) => el.setAttribute(attr, value);
const getAttr = (el, attr) => el.getAttribute(attr);
const getImageIndex = (image) => images.indexOf(image)
const getCurrentImageIndex = () => {
const currentImage = getAttr(imgCarousel, 'src');
return getImageIndex(currentImage);
};
const getArrowLeftImageIndex = (currentIndex) => {
return currentIndex === 0 ? 2 : currentIndex - 1;
};
const getArrowRightImageIndex = (currentIndex) => {
return currentIndex === 2 ? 0 : currentIndex + 1;
};
const activateIndicator = (index) => {
indicators.forEach((el, i) => {
if (el.classList.contains('active')) {
el.classList.remove('active')
};
if (index === i) el.classList.add('active');
})
};
Our helper functions. The first two functions are used for setting and getting respectively the attribute of the element we want. In this case, will be the img
element. The third function is pretty straightforward it's for getting the index of the image. The fourth function is pretty much the same as the third the difference is that here is that we get the image source here and call the getImageIndex
function and return the result. The fourth function getArrowLeftImageIndex
is used for clicking the left arrow and fifth function getArrowRightImageIndex
is used for clicking the right arrow. And lastly the activateIndicator
function is used for updating the correct indicator's class.
Third Section
const intervalSlider = (direction, delay = 1000) => {
let callback = null, getNewIndexFunc = null;
if (direction === 'left') {
getNewIndexFunc = () => getArrowLeftImageIndex(getCurrentImageIndex());
} else {
getNewIndexFunc = () => getArrowRightImageIndex(getCurrentImageIndex());
}
callback = () => {
let newIndex = getNewIndexFunc();
activateIndicator(newIndex);
setAttr(imgCarousel, 'src', images[newIndex]);
}
return () => setInterval(callback, delay);
}
So our third section is just one function. This function is used for sliding functionality of our carousel. Basically what this function does is that when we pass the direction
parameter with a value of "left"
we're gonna get the function that calculates the image index when clicking
the left (<) arrow and we're gonna use that function inside the callback
to calculate the new index of the previous image and update the correct indicator and the correct image based on the newIndex
.
If we pass a value of "right"
for the direction
parameter we're gonna get the function that calculates the next image when clicking the right (>) arrow. The intervalSlider
returns the interval that we're gonna use inside our click events and also you can change
the delay
but my default is 1 second.
Fourth Section
const leftInterval = intervalSlider('left');
const rightInterval = intervalSlider('right');
let left = null, right = null;
arrowLeft.addEventListener('click', (e) => {
const newIndex = getArrowLeftImageIndex(getCurrentImageIndex());
activateIndicator(newIndex);
right && clearInterval(right);
if (!left) {
left = leftInterval()
}
setAttr(imgCarousel, 'src', images[newIndex]);
});
arrowRight.addEventListener('click', (e) => {
const newIndex = getArrowRightImageIndex(getCurrentImageIndex());
activateIndicator(newIndex);
left && clearInterval(left);
if (!right) {
right = rightInterval();
}
setAttr(imgCarousel, 'src', images[newIndex]);
});
Lastly, our fourth section. First we declare the functions that returns the interval and we have the left
and right
variables to hold the interval so that we can use them later in our click events. So in our arrowLeft
click event callback, we get the new index
specifically the previous index of the current index, after that we call the activateIndicator
function passing the newIndex
. If we have an interval value for the right
value, we're gonna clear
that interval using the clearInterval
function. After that, if our left
variable does not have a value, we're gonna start the interval using the leftInterval
and lastly we're gonna update the
image. In our arrowRight
click event callback has almost have the same logic as the arrowLeft
click event callback, but in the arrowRight
we get the next index of the current index and also we're
gonna clear the interval of the left
variable if it has a value and start the right
interval if it's not already started. After that, update the image.
You can add additional functionalities to this carousel, like pausing or anything that comes up in your mind.
Top comments (3)
thx.
No problem man.
If you don't mind, may I ask for the source code for this?