Why another JS slider post?
I wanted to title this post “Yet Another Javascript Slider”, because there are literally hundreds of scripts and packages out there, along with tutorials, that can show you how to write a javascript slider. So why would I spend my time throwing out yet another example of one? Well, throughout my journeys across the interwebs, I found two things that were pervasive among most of the tutorials I came across:
- They claimed they were “javascript”, but were reliant on jQuery
(not that I dislike jQuery, I absolutely love what it can do, but what if I needed/wanted something that wasn’t library dependent?) - They were really just robust packages with a lot of bells and whistles, when my needs in some cases were to have a very simple slider, with nothing more than the basic features (prev/next/play/resize)
We recently had a project where the spec requirement was just that: a simple testimonial slider and nothing more. Surely I wouldn’t need to go down the route of including a robust package SlickJS, right? Even the most lightweight solutions such as Cycle2 or TinySlider were still just too much code for what I needed to do. As the adage goes, I realized that if I wanted to find something as lean as I was envisioning, that would also cover the functionality needs of the project, I would likely just need to write it myself.
Once I had done so and it was deployed, I realized that this little script was pretty darn useful, and might serve someone else as a great jumping off point if they have similar needs. And I call it a “boilerplate”, because you could easily use it as a starting point for something much more ambitious. So that, my friends, is my reasoning for this post…in hopes that someone somewhere will be able to use what I’ve written as just a simple boilerplate to either satisfy some minimum requirements, or perhaps to use as a jumping off point for something more ambitious (however, you’ll probably just use a package at that point). Either way, the name of the game was to keep things as minimal, and compatible, as possible.
The Requirements
I needed my simple slider to do three things:
- Transition the content slides gracefully
- Respond to next and previous navigation
- Be responsive, and mobile device friendly
I want to walk you through how I went about this, but I also want to say ahead of time that this not a tutorial in HTML/CSS/Javascript necessarily, and assumes you have a basic understanding of all three of these disciplines before going down the rabbit hole. And, more importantly, this is not a copy and paste job. The intention behind this script is to give you a clean and minimal starting place that you can adapt to your own markup, styling and functionality needs. Like I said before, if you’re looking for a complete package that will get you moving without much additional customization or coding, then there’s tons of solutions out there already. This is for those of you that might have been in similar positions and really just need the fundamentals, or want a deeper understanding of how these sliders work in the first place.
OK, let’s get building!
Markup
Our slider content is simple, and thus it’s markup is equally simple. We need a wrapper for the entire slideshow, in case we need to position elements around it, without disturbing the slide count. We also need a wrapper around the slides to contain the slides and iterations, and finally we need some basic navigation:
<div class="slide-wrap">
<div class="slideshow">
<div class="slide-entry">
<div class="slide-content"></div>
</div>
<ul class="slide-nav">
<li id="prev-slide"><i>«</i></li>
<li id="next-slide"><i>»</i></li>
</ul> <!-- end .slide-nav -->
</div> <!-- end .slideshow -->
</div> <!-- end .slide-wrap -->
Styling
Next, let’s set up some essential CSS that would be included on just about any slider. Again, this assumes you will understand the basics of CSS to know how to get this kind of functionality working, but I’ll break it down a bit for those that might need a little extra understanding, as CSS can be deceptively and surprisingly difficult:
/* contain the main slider */
.slide-wrap {
position: relative;
max-width:1000px;
margin: 0 auto;
transition:300ms;
}
/* ensure that we can adjust z-index or position elements absolutely within the slideshow itself (e.g., navigation) */
.slideshow {
position: relative;
}
/* set all slides as absolute positioning so they will stack on top of each other, and hidden from one another */
.slide-entry {
position: absolute;
top: 0;
left: 0;
right: 0;
margin:0 auto;
transition:300ms;
text-align: center;
opacity: 0;
visibility: hidden;
padding: 20px 5%;
z-index: 20;
&.active { // this is how we transition the slides in/out
opacity: 1;
visibility: visible;
}
}
/* set up our basic left/right navigation, would should ideally be vertically centered */
.slide-nav {
display: flex;
justify-content: space-between;
align-items:center;
position: absolute;
left: 0;
bottom: 0;
top: 50%;
transform: translateY(-50%);
width: 100%;
list-style: none;
padding: 0;
z-index: 0;
}
.slide-nav li {
cursor: pointer;
color:#ccc;
font-size:25px;
}
/* pull our navigation out of the slider container to avoid z-index issues */
#prev-slide {
margin-left:-20px;
}
#next-slide {
margin-right:-20px;
}
Functionality
Now the fun part begins, making this slider actually do something! I’m going to go piece by piece, but I’ve also included a CodePen at the end with a fully functional prototype for you to play with, in case you are the kind of learner like I am, who really likes to reverse engineer stuff. 🤔
Initial Structure
Let’s begin with something we should always be doing when coding: preparing for the worst case scenario. In this case, the slideshow script should only run when the slideshow is present:
const slideshow = document.querySelector('.slide-wrap');
if (slideshow != null ) { //make sure we don't run this script if the slideshow is not present
}
Let’s set up our variables. Comments in the code will explain further:
let slides = document.querySelectorAll('.slide-entry'), // grab all slides
slideCount = slides.length, //count slides
currentSlide = 0, // find a starting place for the current slide
slideHeight = null, // we'll need this later for height calculation
initialHeight = slides[0].clientHeight; // find the height of the first slide
As it stands, all our slides are hidden. Let’s activate the first slide on page load, and set the height of that slide to the slideshow itself:
slides[0].classList.add('active');
slideshow.style.height = initialHeight + 'px';
Next/Previous events
We’re on our way! Now let’s build in the most basic of all slider functionality: the next/previous functionality. Again, comments on each line so you can get some insight as to what it is happening here:
function moveToSlide(n) { // declare our slide navigation function
slides[currentSlide].className = 'slide-entry'; // assign the slide HTML element
currentSlide = (n+slideCount)%slideCount; // determine current slide (for prev/next functions)
slides[currentSlide].className = 'slide-entry active'; //if it's the current slide, add active class
slideHeight = slides[currentSlide].clientHeight; // get the height of the current slide
slideshow.style.height = slideHeight + 'px'; // set the height of the slides
window.addEventListener('resize', function(){ // if the browser resizes
resizedSlideHeight = slides[currentSlide].clientHeight; // get current slide height
slideshow.style.height = resizedSlideHeight + 'px'; // update the height of the slideshow
});
}
We now have a slider that is functional and has all the pieces we need for it to be navigable, but there are no event listeners (e.g., if someone clicks the next button). All we need is two additional functions that will determine whether we need to move forward or backward, depending on which navigation property is clicked. The elegance of this particular solution is that it will come along with an infinite loop:
function nextSlide(e){
moveToSlide(currentSlide+1); // add one to index, move to the next
};
function prevSlide(e){
moveToSlide(currentSlide+-1); //remove one from index, move to the last
};
And now all that is left is to hook up our handlers and their respective elements so they can be clicked, and have the slideshow transition to the right slide:
const slideHandlers = { nextSlide: function(element){ // establish the method to accept any HTML element document.querySelector(element).addEventListener('click',nextSlide); // hook up the selector }, prevSlide: function(element){ document.querySelector(element).addEventListener('click',prevSlide); } } /* Hook up the individual HTML elements to the functions */ slideHandlers.nextSlide('#next-slide'); slideHandlers.prevSlide('#prev-slide');
Initialize the Slideshow
By this point, we have a fully functional content slider that will respond to next/previous clicks and respond to the respective heights of each slide, but unless the user does anything specifically to interact with the slideshow, it won’t go anywhere. While my personal use case did not require this, it’s a fairly easy addition:
// optional autoplay function
setInterval(function(){ // note: using setInterval vs. setTimeout
nextSlide(); // run our nextSlide() function from above
},8000); // change this timing function to change the slideshow speed (currently 8s)
Now the slideshow will be moving through all it’s slides on it’s own, without any user interaction. We can, of course, go much further with this, extending the script so that it will cease to transition on it’s own if a user begins to interact with it, but that’s something I feel that we can individually decide and add on our own, given that it wouldn’t be too terribly complex of an addition if it’s needed. Again, the point of this brief tutorial and script is to provide the basics.
Keyboard Navigation
Clicking through a slideshow is one way that a user interacts, but keyboard navigation is important to have and is a really simple addition, to boot. There are two components. The first will be revising our nextSlide and prevSlide functions to detect keyboard events:
function nextSlide(e){
moveToSlide(currentSlide+1);
let code = e.keyCode;
if( code == 39 ) { //right arrow key
moveToSlide(currentSlide+1);
}
};
function prevSlide(e){
moveToSlide(currentSlide+-1);
let code = e.keyCode;
if( code == 37 ) { //left arrow key
moveToSlide(currentSlide+-1);
}
};
The second piece will be hooking up the specific event ‘keydown’ listeners and attaching the proper next/prev functions to them.
const slideHandlers = {
nextSlide: function(element){
document.querySelector(element).addEventListener('click',nextSlide);
document.body.addEventListener('keydown',nextSlide, false); // if a key is pressed, attach a listener to find out which one
},
prevSlide: function(element){
document.querySelector(element).addEventListener('click',prevSlide);
document.body.addEventListener('keydown',prevSlide, false);
}
}
And that’s it! Our slider is fully functional, along with responsive and dynamic heights and keyboard navigation. It can accept any kind of HTML content, whether it’s text, images or even video!
Bonus: Swipe Navigation
The only thing that could be the cherry on top, would be to ensure that it works with swipe events. Perhaps this isn’t something essential for a content slider on a website to function, but at this point, we feel that it’s expected user behavior. Thankfully, this is also a rather simple addition, thanks to the wonderful work of Kirupa! I won’t explain this code line by line, as Kirupa does a much better job than I could:
// Detect swipe events for touch devices, credit @ https://www.kirupa.com/html5/detecting_touch_swipe_gestures.htm
let initialX = null;
let initialY = null;
function startTouch(e) {
initialX = e.touches[0].clientX;
initialY = e.touches[0].clientY;
};
function moveTouch(e) {
if (initialX === null) {
return;
}
if (initialY === null) {
return;
}
let currentX = e.touches[0].clientX;
let currentY = e.touches[0].clientY;
let diffX = initialX - currentX;
let diffY = initialY - currentY;
if (Math.abs(diffX) > Math.abs(diffY)) {
if (diffX > 0) { // swiped left
moveToSlide(currentSlide+1); // hooking up our next slide function
}
else { // swiped right
moveToSlide(currentSlide+-1); // hooking up our prev slide function
}
}
initialX = null;
initialY = null;
e.preventDefault();
};
Completed Slider
That wraps up our very basic, yet very functional and compatible, content slider!
Like I mentioned a few times, this slider is really intended for the most basic of purposes. It’s not feature rich, by any means, but it’s functional, library independent, and gives you only what you might need to achieve the design and functionality goals of your project, without all the overhead. And, it should be easy to extend. On that note, if you feel you can improve or add to it, please fork it on the Codepen and let’s talk!