


How to Build a Responsive Image Slider Using Vanilla JavaScript
An image slider is a great way to display multiple images in a compact space while maintaining a smooth user experience. In this tutorial, we will build a simple yet fully responsive image slider using only HTML, CSS, and JavaScript—no libraries required!
Final Output Preview
Before we dive into the code, here is a preview of what we will build:
Step 1: Setting Up the HTML Structure
We'll start by creating a simple HTML file with the basic structure of the slider.
<h1>Slider Using Vanilla Js</h1>
<div class="sliderContainer">
<div class="slider">
<div class="slide"><img src="./images/img-1.webp" alt=""></div>
<div class="slide"><img src="./images/img-2.webp" alt=""></div>
<div class="slide"><img src="./images/img-3.webp" alt=""></div>
<div class="slide"><img src="./images/img-4.webp" alt=""></div>
<div class="slide"><img src="./images/img-5.webp" alt=""></div>
<div class="slide"><img src="./images/img-6.webp" alt=""></div>
</div>
<!-- arrows -->
<div class="arrows">
<button class="prev">❮</button>
<button class="next">❯</button>
</div>
<!-- navigation dots -->
<div class="dots">
<!-- we will add these dots using Js -->
<!-- <span class="dot active"></span>
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span> -->
</div>
Explanation:
- We create a
sliderContainer
to hold all theslider
,arrows
anddots
it is work as a wrapper element for all of them. - we add a
slider
div containing multiple slide div which furthur containing an imageimg
elements. - We added
prev
andnext
buttons to navigate through the images. - We added
dots
which will containing navigation dots
Step 2: Styling the Slider with CSS
Next, let's add CSS to make the slider look visually appealing and responsive.
*,
*::before,
*::after {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 20px;
font-family: Arial, Helvetica, sans-serif;
background: linear-gradient(135deg, #ff9a9e, #fad0c4);
}
body>h1 {
text-transform: capitalize;
color: white;
text-shadow: 0px 2px 5px rgba(0, 0, 0, 0.373);
margin-bottom: 1rem;
font-size: clamp(2rem, 3vw, 2.5rem);
}
.sliderContainer {
max-width: 600px;
overflow: hidden;
border-radius: 15px;
position: relative;
isolation: isolate;
}
.slider {
display: flex;
transition: 300ms ease;
}
.slider .slide {
flex-shrink: 0;
width: 100%;
}
.slider .slide img {
width: 100%;
height: 100%;
object-fit: cover;
}
.sliderContainer .arrows {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 96%;
left: 2%;
display: flex;
justify-content: space-between;
}
.sliderContainer .arrows button {
border: none;
background-color: rgba(0, 0, 0, 0.495);
color: white;
font-size: clamp(1.2rem, 1.5vw, 1.8rem);
width: 45px;
height: 45px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
transition: 300ms ease;
cursor: pointer;
}
.sliderContainer .arrows button:hover {
background-color: rgba(0, 0, 0, 0.736);
transform: scale(1.1);
}
.sliderContainer .arrows button:active {
background-color: rgba(0, 0, 0, 0.884);
transform: scale(1);
}
.sliderContainer .dots {
display: flex;
gap: 0.5rem;
justify-content: center;
align-items: center;
position: absolute;
bottom: 0.5rem;
width: 100%;
/* padding: 1rem;
background-color: red; */
}
.sliderContainer .dots .dot {
width: 12px;
height: 12px;
border-radius: 50%;
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.444);
background-color: rgba(255, 255, 255, 0.495);
transition: 200ms ease;
cursor: pointer;
}
.sliderContainer .dots .dot.active {
background-color: white;
transform: scale(1.3);
}
.sliderContainer .dots .dot:hover {
background-color: white;
}
Explanation:
- The
sliderContainer
holds the elements of slider so we give it styling ofposition : relative
so that all the absolute elements move according to its bouderies - The
slider
usesdisplay: flex
to position slides in a row. - Each
img
inside theslide
takes up 100% of the container width. - Navigation buttons are positioned at the sides with absolute positioning.
Step 3: Adding JavaScript for Functionality
Now, let's implement the JavaScript functionality to make the slider work.
document.addEventListener('DOMContentLoaded', () => {
initializeSlider();
})
function initializeSlider() {
// Slider setup code goes here
}
Explanation:
The above code waits until the DOM content is fully loaded before running the initializeSlider
function, which will set up the slider.
Part 1: Basic Slider Without Smooth Sliding Effect
1. Select all the HTML elements in JavaScript
const sliderContainer = document.querySelector('.sliderContainer');
const slider = sliderContainer.querySelector('.slider');
const slides = [...slider.querySelectorAll('.slide')];
const nextBtn = sliderContainer.querySelector('.arrows .next')
const prevBtn = sliderContainer.querySelector('.arrows .prev')
const dotsContainer = sliderContainer.querySelector('.dots');
Explanation:
Here, we are using document.querySelector()
to select all the necessary elements for our slider, such as the container, slides, navigation buttons, and dots.
2. Create Slider Variables
// Slider variables
let currentIndex = 0;
let isAnimating = false;
let interval;
Explanation:
In this block, we define some key variables. currentIndex
tracks the currently active slide, isAnimating
prevents multiple animations from running simultaneously, and `interval` is for auto-sliding functionality.
3. Insert Dots Dynamically
// Insert dots
slides.forEach((slide, index) => {
let dot = document.createElement('span');
dot.classList.add('dot');
index == currentIndex && dot.classList.add('active');
dotsContainer.appendChild(dot);
});
// Select all dots in JS
const allDots = [...dotsContainer.querySelectorAll('.dot')];
Explanation:
For each slide, we create a dot element and append it to the dots container. If a dot corresponds to the current slide, it is marked as active.
4. Create updateSlide()
Function
// Create a reusable function to update the slide position
function updateSlide() {
slider.style.transform = `translateX(-${currentIndex * 100}%)`;
// Update dots
allDots.forEach((dot, index) => {
if (index == currentIndex) {
dot.classList.add('active')
} else {
dot.classList.remove('active')
}
});
}
Explanation:
The updateSlide()
function is responsible for moving the slider to the correct position based on the currentIndex
. It also updates the active dot to reflect the current slide.
5. Next and Previous Slide Functions
function nextSlide() { // For next button click
if (isAnimating) return;
isAnimating = true;
currentIndex = (currentIndex + 1) % slides.length;
updateSlide()
setTimeout(() => {
isAnimating = false;
}, 500)
resetAutoSlide()
}
function prevSlide() { // For prev button click
if (isAnimating) return;
isAnimating = true;
currentIndex = (currentIndex - 1 + slides.length) % slides.length;
updateSlide()
setTimeout(() => {
isAnimating = false;
}, 500)
resetAutoSlide()
}
Explanation:
The nextSlide()
and prevSlide()
functions move the slider to the next or previous slide, respectively. We also use setTimeout()
to control animation timing and prevent multiple rapid transitions.
6. Go to Specific Slide Using Dots
function goToSlide(index) { // For dots
if (isAnimating) return;
isAnimating = true;
currentIndex = index;
updateSlide()
setTimeout(() => {
isAnimating = false;
}, 500)
resetAutoSlide()
}
Explanation:
The goToSlide()
function allows the user to jump to a specific slide when clicking on a dot. It works by setting the `currentIndex` to the corresponding dot's index.
7. Auto-slide Functionality
// Reset auto-slide on interaction
function resetAutoSlide() {
clearInterval(interval);
// Re-initialize interval
interval = setInterval(nextSlide, 3000);
}
// Add auto slide functionality
interval = setInterval(nextSlide, 3000);
Explanation:
The resetAutoSlide()
function stops the auto-slide timer and restarts it every time the user interacts with the slider. This ensures that the slides continue automatically after any interaction.
8. Attach Event Listeners to Buttons and Dots
nextBtn.addEventListener('click', nextSlide);
prevBtn.addEventListener('click', prevSlide);
allDots.forEach((dot, index) => {
dot.addEventListener('click', () => {
goToSlide(index)
})
})
Explanation:
We add event listeners to the navigation buttons and dots so that users can click to navigate between slides. Each button triggers the corresponding slide function, such as nextSlide()
or goToSlide(index)
.
This concludes Part 1, where we created a basic slider without smooth transitions. However, in this version, clicking the next button will abruptly jump to the first slide when the last one is reached. To add smooth sliding transitions, we move on to Part 2.
Part 2: Adding Smooth Sliding Effect
1. Change const
to let
for Slide Selection
let slides = [...slider.querySelectorAll('.slide')];
Explanation:
We change const
to let
because the slides list will be updated later after adding the clone nodes.
2. Modify `currentIndex` Value
// Slider variables
let currentIndex = 1;
Explanation:
We initialize currentIndex
to 1
because we are adding a clone of the first slide at the end, so we need to start from the second slide.
3. Clone First and Last Slides for Smooth Looping
// Clone first and last slides for smooth looping
const firstClone = slides[0].cloneNode(true);
const lastClone = slides[slides.length - 1].cloneNode(true);
slider.appendChild(firstClone);
slider.insertBefore(lastClone, slides[0]);
Explanation:
To enable infinite looping, we create clones of the first and last slides and add them to the slider. This gives the illusion of a continuous slider.
4. Set Initial Position After Cloning
// Set initial position to slider after inserting clones
slider.style.transform = `translateX(-${currentIndex * 100}%)`;
// Update slides list
slides = [...slider.querySelectorAll('.slide')];
Explanation:
After adding the clones, we set the initial slider position to ensure it starts at the second slide, and then we update the slides array to include the clones.
5. Ignore Cloned Dots
// Insert dots (ignore cloned slides)
slides.forEach((slide, index) => {
if (index === 0 || index + 1 === slides.length) {
return;
}
let dot = document.createElement('span');
dot.classList.add('dot');
index == currentIndex && dot.classList.add('active');
dotsContainer.appendChild(dot);
});
Explanation:
We make sure to exclude the cloned slides from the dots, as they are not part of the original slides.
6. Update updateSlide()
for Smooth Transitions
function updateSlide() {
slider.style.transition = "transform 0.5s ease";
slider.style.transform = `translateX(-${currentIndex * 100}%)`;
// Update dots (with looping effect)
allDots.forEach((dot, index) => {
dot.classList.remove('active');
});
// Correct the active dot index (since currentIndex includes clones)
if (currentIndex === 0) {
allDots[allDots.length - 1].classList.add("active");
} else if (currentIndex === slides.length - 1) {
allDots[0].classList.add("active");
} else {
allDots[currentIndex - 1].classList.add("active");
}
// Handle infinite looping effect here
setTimeout(() => {
if (currentIndex >= slides.length - 1) {
slider.style.transition = "none";
currentIndex = 1;
slider.style.transform = `translateX(-100%)`;
dots[0].classList.add('active');
} else if (currentIndex <= 0) {
slider.style.transition = "none";
currentIndex = slides.length - 2;
slider.style.transform = `translateX(-${currentIndex * 100}%)`;
dots[dots.length - 1].classList.add('active');
}
}, 500)
}
Explanation:
The updateSlide()
function now includes the smooth transition effect and handles infinite looping. It corrects the active dot index and ensures the slider smoothly loops back to the first slide when reaching the last slide, and vice versa.
7. Update nextSlide()
, prevSlide()
, and goToSlide()
Functions
function nextSlide() { // For next button click
if (isAnimating) return;
isAnimating = true;
currentIndex++;
updateSlide()
setTimeout(() => {
isAnimating = false;
}, 500)
resetAutoSlide()
}
function prevSlide() { // For prev button click
if (isAnimating) return;
isAnimating = true;
currentIndex--;
updateSlide()
setTimeout(() => {
isAnimating = false;
}, 500)
resetAutoSlide()
}
function goToSlide(index) { // For dots
if (isAnimating) return;
isAnimating = true;
currentIndex = index + 1;
updateSlide()
setTimeout(() => {
isAnimating = false;
}, 500)
resetAutoSlide()
}
Explanation:
The nextSlide()
, prevSlide()
, and goToSlide()
functions are updated to accommodate the changes in the cloned slides and to ensure the slider transitions smoothly.
With these changes, the slider now smoothly transitions between slides, and users can navigate seamlessly through them. The infinite looping effect ensures the experience feels fluid and continuous.
full code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Slider Using Vanilla Js</title>
</head>
<style>
*,
*::before,
*::after {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 20px;
font-family: Arial, Helvetica, sans-serif;
background: linear-gradient(135deg, #ff9a9e, #fad0c4);
}
body>h1 {
text-transform: capitalize;
color: white;
text-shadow: 0px 2px 5px rgba(0, 0, 0, 0.373);
margin-bottom: 1rem;
font-size: clamp(2rem, 3vw, 2.5rem);
}
.sliderContainer {
max-width: 600px;
overflow: hidden;
border-radius: 15px;
position: relative;
isolation: isolate;
}
.slider {
display: flex;
transition: 300ms ease;
}
.slider .slide {
flex-shrink: 0;
width: 100%;
}
.slider .slide img {
width: 100%;
height: 100%;
object-fit: cover;
}
.sliderContainer .arrows {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 96%;
left: 2%;
display: flex;
justify-content: space-between;
}
.sliderContainer .arrows button {
border: none;
background-color: rgba(0, 0, 0, 0.495);
color: white;
font-size: clamp(1.2rem, 1.5vw, 1.8rem);
width: 45px;
height: 45px;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
transition: 300ms ease;
cursor: pointer;
}
.sliderContainer .arrows button:hover {
background-color: rgba(0, 0, 0, 0.736);
transform: scale(1.1);
}
.sliderContainer .arrows button:active {
background-color: rgba(0, 0, 0, 0.884);
transform: scale(1);
}
.sliderContainer .dots {
display: flex;
gap: 0.5rem;
justify-content: center;
align-items: center;
position: absolute;
bottom: 0.5rem;
width: 100%;
/* padding: 1rem;
background-color: red; */
}
.sliderContainer .dots .dot {
width: 12px;
height: 12px;
border-radius: 50%;
box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.444);
background-color: rgba(255, 255, 255, 0.495);
transition: 200ms ease;
cursor: pointer;
}
.sliderContainer .dots .dot.active {
background-color: white;
transform: scale(1.3);
}
.sliderContainer .dots .dot:hover {
background-color: white;
}
</style>
<body>
<h1>Slider Using Vanilla Js</h1>
<div class="sliderContainer">
<div class="slider">
<div class="slide"><img src="./images/img-1.webp" alt=""></div>
<div class="slide"><img src="./images/img-2.webp" alt=""></div>
<div class="slide"><img src="./images/img-3.webp" alt=""></div>
<div class="slide"><img src="./images/img-4.webp" alt=""></div>
<div class="slide"><img src="./images/img-5.webp" alt=""></div>
<div class="slide"><img src="./images/img-6.webp" alt=""></div>
</div>
<!-- arrows -->
<div class="arrows">
<button class="prev">❮</button>
<button class="next">❯</button>
</div>
<!-- navigation dots -->
<div class="dots">
<!-- we will add these dots using Js -->
<!-- <span class="dot active"></span>
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span> -->
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
initializeSlider();
})
function initializeSlider() {
const sliderContainer = document.querySelector('.sliderContainer');
const slider = sliderContainer.querySelector('.slider');
let slides = [...slider.querySelectorAll('.slide')];
const nextBtn = sliderContainer.querySelector('.arrows .next')
const prevBtn = sliderContainer.querySelector('.arrows .prev')
const dotsContainer = sliderContainer.querySelector('.dots');
//slider variables
// let currentIndex = 0;
let currentIndex = 1;
let isAnimating = false;
let interval;
//clone first and last slides for smooth looping
const firstClone = slides[0].cloneNode(true);
const lastClone = slides[slides.length - 1].cloneNode(true);
slider.appendChild(firstClone);
slider.insertBefore(lastClone, slides[0]);
// set initial position to slider after inserting clones
slider.style.transform = `translateX(-${currentIndex * 100}%)`;
//update slides list
slides = [...slider.querySelectorAll('.slide')];
//insert dots
slides.forEach((slide, index) => {
//ignore cloned nodes
if (index === 0 || index + 1 === slides.length) {
return;
}
//way 1.
// set first dot as active
// currentIndex == index
// ? dotsContainer.innerHTML += `<span class="dot active"></span>`
// : dotsContainer.innerHTML += `<span class="dot"></span>`;
//way 2.
let dot = document.createElement('span')
dot.classList.add('dot');
index == currentIndex && dot.classList.add('active');
dotsContainer.appendChild(dot);
});
// select all dots in js
const allDots = [...dotsContainer.querySelectorAll('.dot')];
// create a reusable function for updateSlide on every interaction
function updateSlide() {
slider.style.transition = "transform 0.5s ease"
slider.style.transform = `translateX(-${currentIndex * 100}%)`;
//update dots
// allDots.forEach((dot, index) => {
// if (index == currentIndex) {
// dot.classList.add('active')
// } else {
// dot.classList.remove('active')
// }
// });
//update dots (with looping effect)
allDots.forEach((dot,index)=>{ //nutrilize all the dots
dot.classList.remove('active');
})
//correcting the active dot index (since currentIndex includes clones)
if(currentIndex === 0){
allDots[allDots.length-1].classList.add("active");
}else if(currentIndex === slides.length -1){
allDots[0].classList.add("active");
}else{
allDots[currentIndex-1].classList.add("active");
}
//Handle infinit looping effect here
setTimeout(() => {
if (currentIndex >= slides.length - 1) {
slider.style.transition = "none";
currentIndex = 1;
slider.style.transform = `translateX(-100%)`;
dots[0].classList.add('active');
} else if (currentIndex <= 0) {
slider.style.transition = "none";
currentIndex = slides.length - 2;
slider.style.transform = `translateX(-${currentIndex * 100}%)`;
dots[dots.length - 1].classList.add('active');
}
}, 500)
}
function nextSlide() { // for next button click
if (isAnimating) return;
isAnimating = true;
// currentIndex = (currentIndex + 1) % slides.length
currentIndex++;
updateSlide()
setTimeout(() => {
isAnimating = false;
}, 500)
resetAutoSlide()
}
function prevSlide() {// for prev button click
if (isAnimating) return;
isAnimating = true;
// currentIndex = (currentIndex - 1 + slides.length) % slides.length
currentIndex--;
updateSlide()
setTimeout(() => {
isAnimating = false;
}, 500)
resetAutoSlide()
}
function goToSlide(index) {// for dots
if (isAnimating) return;
isAnimating = true;
// currentIndex = index;
currentIndex = index + 1;
updateSlide()
setTimeout(() => {
isAnimating = false;
}, 500)
resetAutoSlide()
}
// reset autoslide on interaction
function resetAutoSlide() {
clearInterval(interval);
//re-initialize interval
interval = setInterval(nextSlide, 3000);
}
//add auto slide functionality
interval = setInterval(nextSlide, 3000);
nextBtn.addEventListener('click', nextSlide);
prevBtn.addEventListener('click', prevSlide);
allDots.forEach((dot, index) => {
dot.addEventListener('click', () => {
goToSlide(index)
})
})
}
</script>
</body>
</html>
Recent blog Posts



Create a Stunning 3D Magnet Effect Button with GSAP


