3D image carousel in pure JavaScript and CSS

Image 3D carousel in Vanilla JS and CSS without external library

Introduction

This page explains how to create an image carousel in pure JavaScript and CSS.

The carousel is composed of five visible images and an invisible image (use to load the next image).

I'm going over the creation in the main lines, but if you want me to detail a point, don't hesitate to contact me.

Here is the final result:

HTML

Let start by creating the images in the HTML. The six images are encapsulted in a main div named carousel. Each image has a unique ID and belongs to the class carousel-image

<div id="carousel">
    <img src="https://lucidar.me/fr/web-dev/files/img0.svg" id="carousel-image-0" class="carousel-image">
    <img src="https://lucidar.me/fr/web-dev/files/img1.svg" id="carousel-image-1" class="carousel-image">
    <img src="https://lucidar.me/fr/web-dev/files/img2.svg" id="carousel-image-2" class="carousel-image">
    <img src="https://lucidar.me/fr/web-dev/files/img3.svg" id="carousel-image-3" class="carousel-image">
    <img src="https://lucidar.me/fr/web-dev/files/img4.svg" id="carousel-image-4" class="carousel-image">
    <img src="https://lucidar.me/fr/web-dev/files/img5.svg" id="carousel-image-5" class="carousel-image">
</div>

CSS

Let's now add some CSS to place the images at there original position. First, we center the carousel in the middle of the page. Of course, you can modify this to your needs.

#carousel {
    width: 100%;
    max-width: 100vw;
    height: 21vh;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    overflow: hidden;
}

Now, let's define the general attributes for all the image:

.carousel-image {
    height: 20vh;
    max-height: 200px;
    position: absolute;
    left: 50%;
    border-radius: 7%;
    box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px;

}

Lastly, we specify each image position, z-index and brightness:

#carousel-image-0 {
    transform: translateX(-50%) translateX(-120%) scale(0.6);
    filter: brightness(40%);
    z-index: 1;
}

#carousel-image-1 {
    transform: translateX(-50%) translateX(-80%) scale(0.8);
    filter: brightness(65%);
    opacity: 1;
    z-index: 2;
}

#carousel-image-2 {
    transform: translateX(-50%) scale(1);
    filter: brightness(100%);
    opacity: 1;
    z-index: 3;
}

#carousel-image-3 {
    transform: translateX(-50%) translateX(80%) scale(0.8);
    filter: brightness(65%);
    opacity: 1;
    z-index: 2;
}

#carousel-image-4 {
    transform: translateX(-50%) translateX(120%) scale(0.6);
    filter: brightness(40%);
    opacity: 1;
    z-index: 1;
}

#carousel-image-5 {
    transform: translateX(-50%) scale(1.5);
    filter: brightness(40%);
    opacity: 0;
    z-index: 1;
}

Here is the intermediate result:

Animations

We define an animation for each motion. For example, the following animation moves the image at position 0 (the image on the left) to the position 5 (the hidden image) :

@keyframes mv0to5 {
    0% { transform: translateX(-50%) translateX(-120%) scale(0.6); filter: brightness(40%); opacity: 1; z-index: 2}
    20% { opacity: 0; }
    100% { transform: translateX(-50%) scale(0.6);  filter: brightness(40%); opacity: 0; z-index:1}
}

In the same way, the following animation is the inverse of the previous one. It moves the image from the position 5 (the hidden image) to position 0 (the image on the left):

@keyframes mv5to0 {
    0% { transform: translateX(-50%) scale(0.6);  filter: brightness(40%); opacity: 0; z-index:1}    
    80% {opacity:0;}
    100% { transform: translateX(-50%) translateX(-120%) scale(0.6);  filter: brightness(40%); opacity: 1; z-index:2}    
}

There are twelve animations, one for each image for each motion (forward and backward).

For each animation, we create a CSS class that perform the motion:

.mv0to5 {
    animation: 0.3s ease-out mv0to5;
    animation-iteration-count: 1;
    animation-fill-mode: forwards;
}

To animate the carousel, just add the class to each images.

JavaScript

Of course, the animation is just performed once. To manage our carousel, we write a JavaScript class as follow:

// Class that manage the carousel
class Carousel {

    // Constructor, initialise the carousel
    constructor() {

    // Set a new image (src) to image at given position (pos)
    setImage(pos, src)

    // Hide an image at given position (pos)
    hideImage(pos)

    // Show an image at given position (pos)
    showImage(pos) 

    // Reset carousel, set image 2 in the middle
    reset()

    // Animate one image forward
    next(nextImage)

    // Animate one image backward
    previous(previousImage) {
}

The constructor

// Constructor, initialise the carousel
constructor() {
    // Get image elements
    this.img = [];
    this.img[0] = document.getElementById("carousel-image-0");
    this.img[1] = document.getElementById("carousel-image-1");
    this.img[2] = document.getElementById("carousel-image-2");
    this.img[3] = document.getElementById("carousel-image-3");
    this.img[4] = document.getElementById("carousel-image-4");
    this.img[5] = document.getElementById("carousel-image-5");

    // Set animation key frames forward and backward
    this.animForward  = ['mv0to5', 'mv1to0', 'mv2to1', 'mv3to2', 'mv4to3', 'mv5to4'];
    this.animBackward = ['mv0to1', 'mv1to2', 'mv2to3', 'mv3to4', 'mv4to5', 'mv5to0'];

    // Reset carousel 
    this.reset();
}

The reset() function remove all the animations and set the main image (at position 2) on image 2:

// Reset carousel, set image 2 in the middle
reset() {
    // Remove animations
    this.img.forEach((image) => {
        this.animForward.forEach((animation) => {
            image.classList.remove( animation );            
        })
        this.animBackward.forEach((animation) => {
            image.classList.remove( animation );            
        })
    })
    this.currentImage=2;
}

The function next(nextImage) animate the carousel one image forward. It update the hidden image if requested, remove all animation classes before adding the new one. Note that animation classes can be forward or backward depending on the last animation. Here we remove both.

// Animate one image forward
// If nextImage is defined, replace the hidden image (at position 5) with the new image
next(nextImage) {

    // Set new image if requested
    if (nextImage !== undefined) this.setImage( 5 , nextImage );

    //  Animate    
    this.img.forEach((image, i) => {    

        // Remove animation
        this.animForward.forEach((animation) => { image.classList.remove( animation ); });
        this.animBackward.forEach((animation) => { image.classList.remove( animation ); });

        // Animate to next image
        image.classList.add( this.animForward[(-this.currentImage+i+8)%6] );
    })

    // Increase index to next image
    this.currentImage = (this.currentImage+1)%6;
}

The function to animate backward is similar to the above.

We also added some function to hide, show or replace an image:

setImage(pos, src) {
    this.img[(pos+this.currentImage+4)%6].src = src;
}

// Hide an image at given position (pos)
hideImage(pos) {
    this.img[(pos+this.currentImage+4)%6].style.visibility = 'hidden';
}

// Show an image at given position (pos)
showImage(pos) {
    this.img[(pos+this.currentImage+4)%6].style.visibility = 'visible';
}

Here is the final result:

See also


Last update : 03/07/2023