Weekly Dev Project #003 — Image Slider
This article is more or less transcript of my free YouTube series about making simple web development projects. If you prefer watching over reading, feel free to visit my channel “Dev Newbs”.
Hey there junior developers and everyone else watching this episode of Weekly Projects. This is the third one so far and the thing we will create today is an Image Slider.
My name is Jacob and I will guide you through the whole process of creating this essential piece of code that you will surely need sooner or later in your career as web developers.
Of course, there are many top-notch quality plugins for creating image galleries, but it is very useful to understand how it works under the hood. And that is exactly what you will learn in this week’s tutorial. So without further ado, let’s get into building!
Setting Up the Project Structure
Of course the first thing we need to do is to create a new folder. I called mine as “Image Slider” and I have placed multiple empty files and one folder inside:
- index.html,
- styles.css,
- slider.js,
- folder “images”
HTML
If you are using Visual Studio Code as I am, you can generate a basic html page by typing “html:5” and then pressing enter. This will generate the minimal viable HTML that can be considered a webpage.
The first text I will modify is replacing the title with the new text “Image Slider”. Next, I will add external dependencies. In my case, it is Font Awesome javascript, which will allow me to use free icons. Keep in mind that the file shared in this tutorial is for me. Yours will have a different name and you need to get it by registering for a free account on Font Awesome website.
<script src="https://kit.fontawesome.com/5121a29d38.js" crossorigin="anonymous"></script>
Another reference I need to link is an external stylesheet document where all the styling will take place.
<link rel="stylesheet" href="styles.css">
That’s all for the head section of the HTML. Now let’s put some content into the body.
The body is quite simple. It will contain one DIV element with the ID “slider” and class “slider”. You might be asking why? Well it is simple. You might have multiple sliders, but if you want to style them identically, it is better to achieve that by giving them a common class rather than applying the same styling to multiple IDs.
The DIV will have child DIVs — one child for each slide we want to include in our slider. All the children will have class “slide” and will contain an IMG tag with the “src” property pointing to an image stored in our “images” folder. This way, even if images are large, they can be loaded instantenously.
<div id="slider" class="slider">
<div class="slide"><img src="images/pexels-black-lu-18779056.jpg"></div>
<div class="slide"><img src="images/pexels-david-gomez-18989316.jpg"></div>
<div class="slide"><img src="images/pexels-feodor-chistyakov-19020447.jpg"></div>
<div class="slide"><img src="images/pexels-naro-k-18900657.jpg"></div>
<div class="slide"><img src="images/pexels-nati-18898429.jpg"></div>
<div class="slide"><img src="images/pexels-nelly-🐰-18994151.jpg"></div>
<div class="slide"><img src="images/pexels-nico-herrmann-18945830.jpg"></div>
<div class="slide"><img src="images/pexels-perry-wunderlich-18889183.jpg"></div>
</div>
As you can see, there are some images already included and they are placed in the folder “images” as well. You can download the same images or similar ones by visiting websites which offer royalty free images like Pexels or Unsplash.
Besides the slider itself, we will need controls to move slides forward and backward. The controls will take the form of chevrons to the left and to the right. Clicking on it will trigger the image slider to switch to the next or previous image respectively.
<div class="controls">
<div class="arrows prev"><i class="fa-solid fa-chevron-left"></i></div>
<div class="arrows next"><i class="fa-solid fa-chevron-right"></i></div>
</div>
Last thing we will include is a reference to a JavaScript file with the slider code. The file is called “slider.js” and we will populate it with the code a little later in this tutorial.
<script src="slider.js" type="application/javascript"></script>
That’s our HTML marked as done. Let’s focus on CSS styling next.
CSS
As usual, we need to start from the top, so let’s style the html tag.
html {
height: 100%;
width: 100%;
}
I like to keep my examples simple, so there will be no crazy overflows. Therefore both width and height of the viewport will be set to 100% fixed.
We will do something similar to body, but with some extra styling on top of that. Let’s see.
body {
background-image: linear-gradient(to right, #8360c3, #2ebf91);
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
font-family: 'Inter', sans-serif;
user-select: none;
}
The background should be nice and interesting and this one is exactly that. You can not technically see it yet, but trust me. Display flex with aligning and justifying will guarantee that the child elements of the body will be nicely centered in the middle of the viewport. Margin and padding set to zero just makes sure there will be no undesired overflow.
So far, all we saw was a bunch of large images and nothing else, this will change after applying this piece of styling for the “slider” class.
.slider {
width: 50%;
height: 50%;
padding: 0px;
margin: 0px;
overflow: hidden;
position: relative;
z-index: 1;
}
The slider should only take half of the width and half of the height of the viewport. There is no padding or margin to keep it simple. Overflow is set to be hidden, so we can finally see and appreciate the cool gradient background.
Position relative and z-index are there for the further styling and child elements.
So far so good…may be just with the expectation of those weird symbols on the right side of the slider. Let’s style those a little, so it does not look so miserable.
.controls {
width: 50%;
height: 50%;
padding: 0px;
margin: 0px;
position: fixed;
border: 10px solid #FFF;
box-shadow: rgb(38, 57, 77) 0px 20px 30px -10px;
z-index: 2;
}
We are doing a little trick here, because the controls have exact placement as our slider, but on top of that, it has fixed position and an extra 10px wide white border and a border shadow, that adds depth and also looks pretty cool. The z-index is set to a higher value, so it is placed on top of the existing slider content. It should work even without it, but you know…just in case.
OK, the chevrons are now in the top left corner. I guess we can call that progress, but let’s finally place them where they belong.
.arrows {
position: absolute;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
color: #FFF;
cursor: pointer;
text-shadow: 1px 1px 2px #333;
transform: translateY(-50%);
}
.arrows.prev {
top: 50%;
left: -70px;
}
.arrows.next {
top: 50%;
right: -70px;
}
By giving the arrows absolute position, they will be linked to the closest parent with non-default position. The closest one is “controls” with fixed position. Because of that reason, the default position would be in the left top corner of controls element. But since we gave the arrows exact width and height and in classes “prev” and “next defined exact position from top, it will now move to that position. If we kept it as is, the arrows would be slightly lower because their top would start in the middle of the slider vertically. We can fix that by using “translateY” with value -50% which shifts the arrows a little to the top, so they are exactly in the middle no matter what the actual height of the slider is at the moment.
The remaining commands style the chevrons so they have correct size, color and nice shadow and of course so it shows as an interactive object when we hover over it — the hand icon appears.
We only need to style a few more elements. Next one is the slide itself.
.slide {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
transition: all 0.5s;
z-index: 2;
}
It should of course take the whole width and height of its parent and should start at the top left corner and end in bottom right. By giving the element absolute position, we allow the child element to leverage different positioning based on its parent.
The z-index is there just in case and transition just adds to the smoothness of switching between images once it works.
The next style we need to do is for the images. It is a fairly simple one.
img {
width: 100%;
height: 100%;
object-fit: cover;
}
We are spreading the area for the image to a whole area of the parent. By giving the image property object-fit set to cover, we are saying: “Take as much space as you need and you are able to take, but make sure the aspect ratio is kept as in the original image.”
The next three classes may not be the fanciest ones — because you can not really see anything when you add them and save changes, but they are important nonetheless.
.active {
transform: translateX(0%);
z-index: 100;
}
.left {
transform: translateX(-100%);
}
.right {
transform: translateX(100%);
}
Class “active” gives style to the currently shown slide and makes sure its z-index is the highest one. Also it sets its horizontal position to a left corner, so it is within the parent “slider” center.
The classes “left” and “right” are there to make sure the images that are immediately before or after viewing are marked accordingly and when their time comes, it will slide in from the correct side.
So far, we have no interactivity and we only see cool backgrounds and the first image in the list of images for the new image slider. Next thing we need to do is to make it alive. Add a sliding feature which will work in a circular method, so once you reach the end of the slides, the next slide will be the first one and in the same way, once you are at the first slide, you can go to the previous one which should give you the last one from the list.
JavaScript
First thing we need to make sure is that the HTML structure has been loaded. So we place our whole code into the event listener code under the global object “window” which is triggered on the “load” event. So we know we will have all the necessary information for the slider to begin with.
window.addEventListener("load", function(){
// our code here
});
As usual, we need to first identify certain objects and make reference to them for the future.
let slides = document.getElementsByClassName("slide")
let slideCount = slides.length
let prev = document.querySelector(".prev")
let next = document.querySelector(".next")
let position = 0
let hijacked = false;
The “slides” variable references all the slides from our slider. The “slideCount” defines how many images we will be working with. Next is “prev” and “next” which are both references to those pesky chevron / arrow symbols. The variable “position” represents the currently active index of the slide. The variable “hijacked” is a variable, which is set to true when there is ongoing animation — mostly sliding between images — and you do not want to do anything before one activity finishes. Once the variable is set back to false, you are welcome to trigger animation.
Initially, we need to give certain slides specific classes. The one that is being displayed should have class “active”. The previous slide should have the class “left” and the following slide should be “right”.
slides[slideCount - 1].classList.add("left");
slides[0].classList.add("active");
slides[1].classList.add("right");
Now when we refresh the page and open the developer tools, we will see the classes in their places.
The next thing on the list is a method to return the index of the next slide.
let nextValue = (direction, currentValue, size) => {
if((currentValue == 0) && (direction == "prev")){
return size-1
}
if(currentValue == (size-1) && (direction == "next")){
return 0
}
if(direction == "prev"){
return currentValue-1
}
if(direction == "next"){
return currentValue+1
}
}
The method takes direction either “prev” or “next”, the index of the current slide and total number of slides available. The method handles edge cases when we are finding previous while being at the smallest available index or the case when we reached the last slide and we need to circle back to the first one. Besides these edge cases, it handles standard situations of simply adding or subtracting number one from or to the current value.
Now when we actually have the method that will handle any future slide, we can create two functions to handle actual moving of slides.
let shiftLeft = () => {
slides[position].classList.remove("active")
slides[position].classList.add("left")
position = nextValue("next", position, slideCount)
slides[position].classList.add("active")
slides[position].classList.remove("right")
slides[nextValue("next", position, slideCount)].classList.remove("left")
slides[nextValue("next", position, slideCount)].classList.add("right")
}
let shiftRight = () => {
slides[position].classList.remove("active")
slides[position].classList.add("right")
position = nextValue("prev", position, slideCount)
slides[position].classList.add("active")
slides[position].classList.remove("left")
slides[nextValue("prev", position, slideCount)].classList.remove("right")
slides[nextValue("prev", position, slideCount)].classList.add("left")
}
The functions are called shiftLeft and shiftRight respectively. Their sole purpose is to execute changes of slides between the active one and the one that should take its place.
Both functions remove the classes from respective slides. Then the function nextValue is called to get the next position. That is followed by assignment of the classes to respective slides. The only difference between both functions is the word “left” or “right” which are used as synonyms to “prev” and “next”, so we could refactor the code into one function with an argument. That is something you can try on your own. I am too lazy and too eager to finish this tutorial, so I am moving to the next part of the code now.
Once we have these functions allowing the slide animation, all we need is to add triggers. We will use two approaches. We will trigger the slider with chevrons and also with the arrows on the keyboard. Let’s do the keyboard first.
document.addEventListener("keydown", function(event){
if(!hijacked){
if(event.key == "ArrowLeft"){
hijacked = true
shiftRight()
setTimeout(function(){
hijacked = false
}, 500)
}
if(event.key == "ArrowRight"){
hijacked = true
shiftLeft()
setTimeout(function(){
hijacked = false
}, 500)
}
}
})
This event listener waits for the key to be pressed. If the slider is not in the process of sliding to another slide — hijacked variable is false — and the key pressed is either “left arrow” or “right arrow”, then the slider moves either one slide forward or one slide backward. It depends on the arrow key pressed. Also the hijacked variable is set to true value and setTimeout function is invoked. The sole purpose of this function is to restore the hijacked variable back to value false after 500ms, so we can again move slides.
The following event listeners do exactly the same thing as the previous ones with the keyboard. The only exception is that event is “click” and targets are chevrons.
prev.addEventListener("click", function(event){
if(!hijacked){
hijacked = true
shiftRight()
setTimeout(function(){
hijacked = false
}, 500)
}
})
next.addEventListener("click", function(event){
if(!hijacked){
hijacked = true
shiftLeft()
setTimeout(function(){
hijacked = false
}, 500)
}
})
Once we save the changes, we are able to move the slides by pressing the arrow to the left or right. And that means that our slider is done.
There are many things that you can do to improve the code and the slider overall . The first thing is to refactor the code, so it does not contain code that is duplicated just like with shift methods. Another improvement would be to add swipe functionality, so you could move slides on the mobile devices even without keyboard or chevron symbols. And one very important thing that comes to my mind is to actually modify the code in a way that you can create multiple sliders on the same page and each one would be independent of the others. The way the code is written right now, it might affect all the sliders at once, which is not always the desired behavior.
If you would like to see the whole code in one place, feel free to check it in my public GitHub repository here:
https://github.com/j-cup/weekly-dev-project/tree/main/003-image-slider
If you have any questions or suggestions, feel free to leave them in the comments below. Don’t forget to like, subscribe, and hit the notification bell to stay updated on future tutorials.
I hope you enjoyed this tutorial and I can not wait to see you with the next one!