How to Create an Image Slider using HTML + CSS + JS
A great UI component for a collection of images
Also known as an Image Carousel, this UI component puts a collection of images that might otherwise eat up whitespace into a single frame. With a uniform location for these images, users can slide from photo to photo, or an auto-sliding script can do it for the user. Without further ado, let’s create an image slider using HTML, CSS, and JS.
The Outline
Before we start writing any code, let’s conceptualize what our image slider looks like and how it behaves. The animation below shows how our image slider will behave when finished.
The image inside the blue container is the display image. Everything inside the container will be visible, while everything outside will be hidden. The red container, which contains each image, will move left to display the image to the right and vice versa.
In the blue container, there are navigation buttons for changing the display image. The arrows on the left and right sides change the display image to the next left or right image. If the display image is on either end, the image container will loop back to the opposite end. The dots at the bottom allow the user to navigate to a specific image.
Right off the bat, it’s apparent that we are going to need to use the position
property in CSS to layer our HTML elements on top of each other — the visual below shows how our image slider might be perceived in a 3D space.
The blue container and red container exist in the same plane, but the green navigation container is in front of everything else, which allows the pointer events to be caught by the navigation buttons.
The HTML
Before we start, here’s the Replit page if you need to see the finished code.
First, we’ll start by writing our HTML, which is the shortest part of this tutorial. The HTML is as follows:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Slider</title>
</head>
<style>
/* Insert styles here */
</style>
<body>
<main>
<div class="slider-container slider-dimensions">
<div class="slider">
<div data-img-url="image_1.jpg" class="slider-dimensions"></div>
<div data-img-url="image_2.jpg" class="slider-dimensions"></div>
<div data-img-url="image_3.jpg" class="slider-dimensions"></div>
</div>
<div class="nav-container slider-dimensions">
<button data-index-change="-1"><</button>
<div class="index-container">
<button></button>
<button></button>
<button></button>
</div>
<button data-index-change="1">></button>
</div>
</div>
</main>
<script>
/* Insert scripts here */
</script>
</body>
</html>
For the images themselves, we are using <div>
tags with the attribute data-img-url="*.png"
and we’ll use JavaScript to add the CSSbackground-image
property, which works better for a responsive design than using <img>
tags.
Inside <div class="index-container">
there are 3 buttons, which are the navigation buttons at the bottom center. If you add or remove images to the slider, make sure you add or remove buttons, so the number of buttons correlates to the number of images.
If you need images to test your image slider, I recommend unsplash.com. They have a sizable collection of freely usable images.
Let’s look at what HTML elements correlate with the containers in the sketch image from earlier.
Before we write the CSS, let’s insert the JavaScript needed to render the images. Put this code in the <script>
tag.
const imgs = document.querySelectorAll('[data-img-url]');
imgs.forEach((div) => {
div.style.backgroundImage = `url(./${div.getAttribute('data-img-url')})`;
});
This code loops through each <div>
that has the attribute [data-img-url]
and adds the background-image
property to each<div>
with its respective [data-img-url]
attribute.
We can’t see the images yet because the <div>
s currently have a height of 0, but they’ll become visible as we write our CSS.
The CSS
Let’s add our styling to the slider, class by class. First, we have some base styles.
body {
margin: 0;
}
main {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.slider-dimensions {
width: min(100vw, 720px);
aspect-ratio: 3 / 2;
}
The body
and main
style declarations center the image slider which doesn’t affect the slider itself. The .slider-dimensions
class is a utility class that is applied to the slider container, navigation container, and each slider image. It allows for a responsive width that accommodates mobile devices. You can change the second argument in the min
function, initially 720px
, to increase or decrease the max width of the image slider.
Here’s the CSS selector for .slider-container
:
.slider-container {
position: relative;
overflow: hidden;
background-color: lightgray;
}
The position: relative;
allows us to keep child elements that have position: absolute;
within this element’s borders. overflow: hidden;
is especially important, as it allows us to hide the images not being shown.
Now let’s add the CSS for the .slider
class and the images themselves, .slider > div
:
.slider {
position: absolute;
display: flex;
transform: translateX(-0%);
transition: transform 1s;
}
.slider > div {
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
The position: absolute;
property removes the element from the document flow and moves it to the top of the document or to the closest ancestor element that has a position
value that’s not static
, which is the .slider-container
in this instance.
In the .slider
selector, the position: absolute;
and display: flex;
allow each image <div>
to fill the width of the slider while not breaking to a new line. The background-*
properties in .slider > div
format the background image to fit within whatever dimensions the slider-container
has. Below are two examples of what this looks like.
By now you should see your first image in the center of the website. If you don’t, double-check your CSS and HTML, and make sure you added the JS script I included earlier.
Below is the styling for the .nav-container
.
.nav-container {
position: absolute;
padding: 0 20px 20px;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
background-image: radial-gradient(transparent, rgba(0,0,0,.3));
opacity: 0;
transition: opacity .2s;
}
.nav-container:hover {
opacity: 1;
}
The .nav-container
is only displayed when the mouse is over it due to the .nav-container:hover
selector. The other style properties are there to lay out the buttons. Lastly, we’ll style the navigation buttons and add conditional mobile styling with the @media
query.
.index-container {
align-self: flex-end;
display: flex;
gap: 30px;
}
.index-container > button {
width: 20px;
height: 20px;
border: none;
border-radius: 20px;
background-color: rgba(255,255,255,.3);
transition: background-color .2s;
cursor: pointer;
}
.index-container > button:hover {
background-color: rgba(255,255,255,.7);
}
button[data-index-change] {
font-size: 40px;
color: white;
background-color: transparent;
border: none;
cursor: pointer;
}
@media (max-width: 500px) {
.nav-container {
padding: 0 0 10px;
}
.index-container {
gap: 20px;
}
}
With all our CSS done, your image-slider should look something like this:
Now let’s make this image slider interactive!
The JavaScript
If you’ve been following, along, your <script>
tag should resemble the following:
<script>
const imgs = document.querySelectorAll('[data-img-url]');
imgs.forEach((div) => {
div.style.backgroundImage = `url(./${div.getAttribute('data-img-url')})`;
});
</script>
We’re going to add the rest of the JavaScript after the imgs.forEach
function call. First, let’s define the variables that reference the various slider tags.
const slider = document.querySelector('.slider');
const indexButtons = document.querySelectorAll('.index-container > button');
const arrowButtons = document.querySelectorAll('[data-index-change]');
let currIndex = 0;
the currIndex
variable will be used to keep track of what image is being currently shown and will be updated as needed.
Next is the slide
function, which changes the image slider from displaying the image at currIndex
to the parameter, nextIndex
.
function slide(nextIndex) {
if (nextIndex < 0) nextIndex = imgs.length - 1;
if (nextIndex >= imgs.length) nextIndex = 0;
indexButtons[currIndex].style.backgroundColor = '';
indexButtons[nextIndex].style.backgroundColor = 'white';
slider.style.transform = `translateX(-${(nextIndex / imgs.length) * 100}%)`;
currIndex = nextIndex;
}
The if
statements handle the 2 cases where the left arrow was clicked while displaying the first image, or the right arrow was clicked while displaying the last image. In either case, nextIndex
becomes the index for the last image or the first image, respectively. The next two lines change the background color on the index buttons to reflect the image change. The following line uses the transform: translateX();
function to move the .slider
element using the calculation, (nextIndex / img.length) * 100
in a negative percentage context. For example, if there were 3 images and nextIndex
equals 1, the style declaration will be computed as transform: translateX(-33.3333%);
. By using a percentage value and the number of images, we can account for a variable number of images in our image slider.
If you recall the CSS we wrote earlier, we had some transition
properties that will come to fruition when we call our slide
function. If you want to test it out now, open your HTML file in a browser, go to the developer console, and call slide
with an index number as an argument.
Before we move on, we need to add this one line that sets a white background to the first index button since it’s initially the first image shown.
indexButtons[0].style.backgroundColor = 'white';
Now let’s hook up our different navigation buttons to the slide
function using event listeners.
indexButtons.forEach((button, index) => {
button.addEventListener('click', () => {
slide(index);
});
});
arrowButtons.forEach((button) => {
button.addEventListener('click', () => {
const indexChange = +button.getAttribute('data-index-change');
slide(currIndex + indexChange);
});
});
The indexButtons.forEach
function call adds an event listener to each button, which calls the slide function with its respective index when clicked. The arrowButtons.forEach
is similar but has some more logic. The indexChange
variable is the value of the data-index-change
attribute on the button clicked, which is either -1 or 1, converted to a number using an unary plus. We then call slide
with nextIndex
equaling currIndex + indexChange
.
That’s it! try clicking the different navigation buttons and play around with the image slider. Remember that you’ll need to hover over the slider to make the navigation buttons appear.
Bonus: Auto-sliding feature
Are you interested in souping up your image slider? Do you want to automate the process of clicking to see the next photo? Because honestly, your users don’t have the time or energy to do that. Let’s add some more JavaScript to create an auto-sliding feature.
We’ll want our slider to slide to the next image automatically, but not when the user is hovering over the image slider. Let’s first add the code necessary to start and stop the automatic sliding feature.
const navContainer = document.querySelector('.nav-container');
let id;
let isSliding = false;
function startAutoSlide() {
if (!isSliding) {
id = setInterval(() => slide(currIndex + 1), 5000);
isSliding = true;
}
};
function stopAutoSlide() {
clearInterval(id);
isSliding = false;
};
startAutoSlide();
When startAutoSlide
is called, it creates a setInterval
which calls slide
every 5 seconds. id
is the id of the current interval, which is cleared when stopAutoSlide
is called. isSliding
is a guard boolean to handle the case where startAutoSlide
is called twice in a row, which would cause the loss of the id to the former setInterval
.
Now let’s add our event listeners to stop the auto slider when the user is hovering over the image slider.
navContainer.addEventListener('mouseover', () => stopAutoSlide());
navContainer.addEventListener('mouseout', () => startAutoSlide());
document.addEventListener('touchstart', (e) => {
if (e.target !== navContainer && !navContainer.contains(e.target))
startAutoSlide();
});
This code requires some explaining. On desktop, the mouseover
and mouseout
events work as you’d expect, but on mobile, the user has to click on the image slider to trigger the mouseover
event.
However, after testing the image slider on an iPhone, triggering the mouseout
event was impossible. So, the touchstart
event listener handles the case where the user clicks outside of the image slider on a mobile device. The if
statement checks if the user actually clicked outside the image slider.
The finished product should behave like this:
Conclusion
With the basic technologies of the web, we have created a robust image slider that works on desktop and mobile devices; it also has an auto-sliding feature, if you chose to add it. Your users will appreciate the interactive and intuitive design of this UI component.