Published
I recently built a website for my friend Kevin to promote his skills, one of them being photography. He asked for an image carousel to show off some of his photos—so, instead of picking up one of the thousands of existing image carousel libraries, I decided to roll my own. You know, for kicks.
Check the carousel in action right here.
Since my last post about carousels got lot of attention, I decided to write this post you now find yourself reading.
I wanted to keep the markup dead simple because, when I was starting out with web dev, I actually found a lot of the carousel libraries out there to be overly complicated, some with really weird markup requirements. So, after some thought, I came up with the following structure for the markup:
1<div id="fancy-carousel"> 2 <img src="./images/01.jpg" alt="Alt text goes here :-)" /> 3 <img src="./images/02.jpg" alt="Alt text goes here :-o" /> 4 <img src="./images/03.jpg" alt="Alt text goes here ;-p" /> 5</div> 6
So, basically, I have a div
with the id
fancy-carousel, I put images in it, and the JavaScript does all the work for me. No data attributes or complicated markup structure. After everything is put together, it looks something like this:
See the Pen Fancy Carousel by Daniel Cortes (@dgca) on CodePen.
So what’s happening is the JS finds our div#fancy-carousel
, creates two new div
s, each stacked on top of each other, and uses the top (as in z-order) one to house the controls—the previous and next arrows.
The div
underneath (let’s call it the ‘display div
’) is responsible for displaying the current image. I created an array out of the image sources, which is then applied as the display div
’s background image. When the next arrow is clicked, the next image source in the array is set as the display div
’s background. Clicking the previous arrow sets the background image to the previous item in the array.
One thing I wanna point out is that a fancy-carousel–active class is being added to the main carousel wrapper, and a hide class is being added to the img
elements as they go through the for
loop that creates the array of image sources. This is so we can conditionally style the carousel if the JS runs. If you open up the Codepen example on the Codepen site and delete all the JS code, you’ll see that the images render how they would normally render on the page.
So here’s the final JS code, with comments to explain what’s going on.
1var fancyCarousel = document.getElementById("fancy-carousel"); 2if (fancyCarousel && fancyCarousel.hasChildNodes()) { 3 // Create the divs that we'll be adding to the DOM to create our markup structure 4 // imagesWrapper will display the image via its backgroundImage CSS property 5 // controlsWrapper will contain the previous and next arrows 6 var imagesWrapper = document.createElement("div"); 7 var controlsWrapper = document.createElement("div"); 8 var prevArrow = document.createElement("div"); 9 var nextArrow = document.createElement("div"); 10 // Add classes to our markup so we can style it later 11 fancyCarousel.classList.add("fancy-carousel--active"); 12 imagesWrapper.classList.add("fancy-carousel__images-wrapper"); 13 controlsWrapper.classList.add("fancy-carousel__controls-wrapper"); 14 prevArrow.classList.add("fancy-carousel__prev-arrow"); 15 nextArrow.classList.add("fancy-carousel__next-arrow"); 16 // Add the previous and next arrow divs to the controlsWrapper 17 controlsWrapper.appendChild(prevArrow); 18 controlsWrapper.appendChild(nextArrow); 19 // Add the imagesWrapper and controlsWrapper to the carousel wrapper 20 fancyCarousel.appendChild(imagesWrapper); 21 fancyCarousel.appendChild(controlsWrapper); 22 23 // Create an array out of the source attribute of any images in the carousel 24 // Add a 'hide' class to the image as they're being iterated on 25 var childNodes = fancyCarousel.childNodes; 26 var imagesArray = []; 27 for (var i = 0; i < childNodes.length; i++) { 28 var currentNode = childNodes[i]; 29 if (currentNode.nodeName === "IMG") { 30 imagesArray.push(currentNode.src); 31 currentNode.classList.add("hide"); 32 } 33 } 34 35 // Set the imagesWrapper's background image to the first image in the imagesArray 36 imagesWrapper.style.backgroundImage = `url(${imagesArray[0]})`; 37 38 // Here we begin setting up the logic for transitioning between images 39 // First step, set up some variables for the current image index, 40 // and the number of images in the images array 41 var currentImage = 0; 42 var numberOfImages = imagesArray.length; 43 44 // Set up event listeners to call the handleSlideshowArrow() function 45 // when the arrows are clicked 46 prevArrow.addEventListener("click", function () { 47 handleSlideshowArrow("prev"); 48 }); 49 nextArrow.addEventListener("click", function () { 50 handleSlideshowArrow("next"); 51 }); 52 53 // Let's use those currentImage and numberOfImages variables from up there 54 // If 'prev' arrow is clicked, decrease currentImage by one, 55 // else if 'next' arrow is clicked increase currentImage by one 56 // ...plus some extra logic so that we don't go into the negatives 57 // or get to an index that doesn't exist 58 function handleSlideshowArrow(val) { 59 if (val === "prev") { 60 if (currentImage > 0) { 61 currentImage--; 62 } else { 63 currentImage = numberOfImages - 1; 64 } 65 } else if (val === "next") { 66 if (currentImage < numberOfImages - 1) { 67 currentImage++; 68 } else { 69 currentImage = 0; 70 } 71 } 72 // Alright, so we're not actually going to be going to the next image 73 // in this function, but we're going to add a 'fade-out' class to our 74 // imagesWrapper so that we can have a nice fade transition from item 75 // to item 76 imagesWrapper.classList.add("fade-out"); 77 // And we're going to add an event listener that's going to do the 78 // transitioning as soon as the fade out transition ends, so let's go 79 // into that function below 80 imagesWrapper.addEventListener("transitionend", handleTransitionEnd); 81 } 82 83 function handleTransitionEnd() { 84 // So since our fade out transition has ended, we can remove the event listener 85 imagesWrapper.removeEventListener("transitionend", handleTransitionEnd); 86 // We set the background image to the current image in the imagesArray 87 imagesWrapper.style.backgroundImage = 88 "url(" + imagesArray[currentImage] + ")"; 89 // And we remove the fade-out class, which is gonna make our image fade back in 90 imagesWrapper.classList.remove("fade-out"); 91 } 92} 93// And there we go, that's it for the JS 94// Next up, the SCSS 95
1// Since we're going to be stacking divs on top of each other, we have 2// to make the main wrapper position relative 3.fancy-carousel--active { 4 background-color: #49708a; 5 height: 300px; 6 overflow: hidden; 7 position: relative; 8 9 // And those images that we added a 'hide' class to—well, we're not gonna 10 // totally hide them with 'display: none' or 'visibility: hidden', because 11 // we still want screen readers to know about them 12 .hide { 13 left: 100%; 14 position: absolute; 15 top: 100%; 16 } 17} 18 19// Stack the images and controls wrapper, and make 20// them the same size as the main container 21.fancy-carousel__images-wrapper, 22.fancy-carousel__controls-wrapper { 23 bottom: 0; 24 left: 0; 25 position: absolute; 26 right: 0; 27 top: 0; 28} 29 30// Let's center those images in the images wrapper, 31// and set our fade out transition which is what the 32// event handler listens to so that it switches out 33// the images when the image is fully transparent 34.fancy-carousel__images-wrapper { 35 background-position: 50% 50%; 36 background-size: cover; 37 opacity: 1; 38 transition: opacity 0.25s; 39 40 &.fade-out { 41 opacity: 0; 42 } 43} 44 45// Here we set up the base styles of the previous and next arrows 46.fancy-carousel__prev-arrow, 47.fancy-carousel__next-arrow { 48 background-color: black; 49 border-radius: 50%; 50 cursor: pointer; 51 height: 30px; 52 opacity: 0.35; 53 position: absolute; 54 top: 50%; 55 transition: opacity 0.25s; 56 transform: translateY(-50%); 57 width: 30px; 58 59 &:hover { 60 opacity: 0.55; 61 } 62 63 // And we make the arrows themselves out of pseudoelements 64 &::before { 65 border-bottom: 3px solid white; 66 border-left: 3px solid white; 67 content: ""; 68 display: block; 69 height: 10px; 70 left: 50%; 71 position: absolute; 72 top: 50%; 73 width: 10px; 74 } 75} 76 77// Position the arrows 78.fancy-carousel__prev-arrow { 79 left: 5px; 80 81 &::before { 82 transform: translateY(-50%) translateX(-30%) rotate(45deg); 83 } 84} 85 86.fancy-carousel__next-arrow { 87 right: 5px; 88 89 &::before { 90 transform: translateY(-50%) translateX(-60%) rotate(225deg); 91 } 92} 93// And that's it! 94
So yeah, there you go. HTML, SCSS, and vanilla JS carousel! I’m pretty happy with how this turned out. It works super well on mobile as well as desktop, gracefully degrades if someone doesn’t have JS turned on for whatever reason, and is super easy to integrate.
That said, there are three things I want to do to improve this. For one, we don’t need to load all the images right away. Ideally, the images would load as they’re needed. Secondly, I would like to add swipe support so that you don’t have to tap the arrows on touchscreens. Finally, I’d like to make the whole thing class-based instead of ID based. As is, this wouldn’t work if you needed more than one carousel on screen at a time.
Well, that’s it for this post! If you read this far, thanks for sticking with this and I hope you learned something from this write-up.