3D Image Slider Responsive Touch Slider using HTML CSS & JS

Welcome to my Blog, In this article, we will build a 3D Image Slider Responsive Touch Slider using HTML CSS & JS with source code. The image slider has three images with our title or description you can navigate each slider with the right and left arrows with background effect.

3D Image Slider Responsive Touch Slider using HTML CSS & JS Video:

3D Image Slider | Response Touch Slider using HTML CSS & JS

if you want to watch more videos go to YouTube Channel

Image Slider HTML :

Now we discuss HTML of responsive touch image slider firstly I have added font awesome and google font CDN. Now create the main app div and create another div (cardList) inside the app div. The card list div divided into three parts.
1) Add an SVG left arrow icon for moving the image slider left-hand side.
2) Card Wrapper div for store all card images that we see on the front page. I have added three cards current card, the next card, and the previous card.
3) And add another SVG right arrow icon for moving the image slider right-hand side.

<!DOCTYPE html>
<html lang="en" >
<head>
  <meta charset="UTF-8">
  <title>Voyage Slider</title>
  <link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.1/css/all.min.css'>
<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Montserrat&amp;display=swap"rel="stylesheet'>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="app">
	<div class="cardList">
		<button class="cardList__btn btn btn--left">
			<div class="icon">
				<svg>
					<use xlink:href="#arrow-left"></use>
				</svg>
			</div>
		</button>
		<div class="cards__wrapper">
			<div class="card current--card">
				<div class="card__image">
					<img src="1.jpeg" alt="" />
				</div>
			</div>

			<div class="card next--card">
				<div class="card__image">
					<img src="2.jpeg" alt="" />
				</div>
			</div>
			<div class="card previous--card">
				<div class="card__image">
					<img src="3.jpeg" alt="" />
				</div>
			</div>
		</div>
		<button class="cardList__btn btn btn--right">
			<div class="icon">
				<svg>
					<use xlink:href="#arrow-right"></use>
				</svg>
			</div>
		</button>
	</div>
	<!---- and more ----->
</div>

<script src='https://unpkg.com/imagesloaded@4/imagesloaded.pkgd.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.3/gsap.min.js'></script>
<script  src="./script.js"></script>
</body>
</html>

Download Image slider Images : Click Here

After that add a new div (info list) now we adding every card detail like name, location, and description each image slide have a different name, location, or description. you can add more image slides copy and paste a repeated divs. the below code add at the bottom of the app div.

<div class="infoList">
		<div class="info__wrapper">
			<div class="info current--info">
				<h1 class="text name">Highlands</h1>
				<h4 class="text location">Scotland</h4>
				<p class="text description">The mountains are calling</p>
			</div>

			<div class="info next--info">
				<h1 class="text name">Machu Pichu</h1>
				<h4 class="text location">Peru</h4>
				<p class="text description">Adventure is never far away</p>
			</div>

			<div class="info previous--info">
				<h1 class="text name">Chamonix</h1>
				<h4 class="text location">France</h4>
				<p class="text description">Let your dreams come true</p>
			</div>
		</div>
	</div>

So now adding background image of card slide for the current image, next image, and previous image when you click on the left or right arrow the background image automatically change or equal to the front image that currently has. The below code part adding at the bottom of the app div. This is the last part of the main app div.

<div class="app__bg">
		<div class="app__bg__image current--image">
			<img src="1.jpeg" alt="" />
		</div>
		<div class="app__bg__image next--image">
			<img src="2.jpeg" alt="" />
		</div>
		<div class="app__bg__image previous--image">
			<img src="3.jpeg" alt="" />
		</div>
	</div>

The loading wrapper div used to load the page content successfully. if the page content is loaded then hide the loader make sure to place the loader wrapper div after the main app div.

<div class="loading__wrapper">
	<div class="loader--text">Loading...</div>
	<div class="loader">
		<span></span>
	</div>
</div>

After the loader adding a SVG arrow icons.

<svg class="icons" style="display: none;">
	<symbol id="arrow-left" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'>
		<polyline points='328 112 184 256 328 400'
					 style='fill:none;stroke:#fff;stroke-linecap:round;stroke-linejoin:round;stroke-width:48px' />
	</symbol>
	<symbol id="arrow-right" xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'>
		<polyline points='184 112 328 256 184 400'
					 style='fill:none;stroke:#fff;stroke-linecap:round;stroke-linejoin:round;stroke-width:48px' />
	</symbol>
</svg>

Image Slider CSS :

@import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@500;600;700;800&display=swap");
:root {
  --card-width: 200px;
  --card-height: 300px;
  --card-transition-duration: 800ms;
  --card-transition-easing: ease;
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  width: 100%;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  background: rgba(0, 0, 0, 0.787);
  overflow: hidden;
}

button {
  border: none;
  background: none;
  cursor: pointer;
}
button:focus {
  outline: none;
  border: none;
}

.app {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.app__bg {
  position: absolute;
  width: 100%;
  height: 100%;
  z-index: -5;
  filter: blur(8px);
  pointer-events: none;
  user-select: none;
  overflow: hidden;
}
.app__bg::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: #000;
  z-index: 1;
  opacity: 0.8;
}
.app__bg__image {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%) translateX(var(--image-translate-offset, 0));
  width: 180%;
  height: 180%;
  transition: transform 1000ms ease, opacity 1000ms ease;
  overflow: hidden;
}
.app__bg__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.app__bg__image.current--image {
  opacity: 1;
  --image-translate-offset: 0;
}
.app__bg__image.previous--image, .app__bg__image.next--image {
  opacity: 0;
}
.app__bg__image.previous--image {
  --image-translate-offset: -25%;
}
.app__bg__image.next--image {
  --image-translate-offset: 25%;
}

.cardList {
  position: absolute;
  width: calc(3 * var(--card-width));
  height: auto;
}
.cardList__btn {
  --btn-size: 35px;
  width: var(--btn-size);
  height: var(--btn-size);
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 100;
}
.cardList__btn.btn--left {
  left: -5%;
}
.cardList__btn.btn--right {
  right: -5%;
}
.cardList__btn .icon {
  width: 100%;
  height: 100%;
}
.cardList__btn .icon svg {
  width: 100%;
  height: 100%;
}
.cardList .cards__wrapper {
  position: relative;
  width: 100%;
  height: 100%;
  perspective: 1000px;
}

.card {
  --card-translateY-offset: 100vh;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%) translateX(var(--card-translateX-offset)) translateY(var(--card-translateY-offset)) rotateY(var(--card-rotation-offset)) scale(var(--card-scale-offset));
  display: inline-block;
  width: var(--card-width);
  height: var(--card-height);
  transition: transform var(--card-transition-duration) var(--card-transition-easing);
  user-select: none;
}
.card::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: #000;
  z-index: 1;
  transition: opacity var(--card-transition-duration) var(--card-transition-easing);
  opacity: calc(1 - var(--opacity));
}
.card__image {
  position: relative;
  width: 100%;
  height: 100%;
}
.card__image img {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.card.current--card {
  --current-card-rotation-offset: 0;
  --card-translateX-offset: 0;
  --card-rotation-offset: var(--current-card-rotation-offset);
  --card-scale-offset: 1.2;
  --opacity: 0.8;
}
.card.previous--card {
  --card-translateX-offset: calc(-1 * var(--card-width) * 1.1);
  --card-rotation-offset: 25deg;
}
.card.next--card {
  --card-translateX-offset: calc(var(--card-width) * 1.1);
  --card-rotation-offset: -25deg;
}
.card.previous--card, .card.next--card {
  --card-scale-offset: 0.9;
  --opacity: 0.4;
}

.infoList {
  position: absolute;
  width: calc(3 * var(--card-width));
  height: var(--card-height);
  pointer-events: none;
}
.infoList .info__wrapper {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: flex-start;
  align-items: flex-end;
  perspective: 1000px;
  transform-style: preserve-3d;
}

.info {
  margin-bottom: calc(var(--card-height) / 8);
  margin-left: calc(var(--card-width) / 1.5);
  transform: translateZ(2rem);
  transition: transform var(--card-transition-duration) var(--card-transition-easing);
}
.info .text {
  position: relative;
  font-family: "Montserrat";
  font-size: calc(var(--card-width) * var(--text-size-offset, 0.2));
  white-space: nowrap;
  color: #fff;
  width: fit-content;
}
.info .name,
.info .location {
  text-transform: uppercase;
}
.info .location {
  font-weight: 800;
}
.info .location {
  --mg-left: 40px;
  --text-size-offset: 0.12;
  font-weight: 600;
  margin-left: var(--mg-left);
  margin-bottom: calc(var(--mg-left) / 2);
  padding-bottom: 0.8rem;
}
.info .location::before, .info .location::after {
  content: "";
  position: absolute;
  background: #fff;
  left: 0%;
  transform: translate(calc(-1 * var(--mg-left)), -50%);
}
.info .location::before {
  top: 50%;
  width: 20px;
  height: 5px;
}
.info .location::after {
  bottom: 0;
  width: 60px;
  height: 2px;
}
.info .description {
  --text-size-offset: 0.065;
  font-weight: 500;
}
.info.current--info {
  opacity: 1;
  display: block;
}
.info.previous--info, .info.next--info {
  opacity: 0;
  display: none;
}

.loading__wrapper {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background: #000;
  z-index: 200;
}
.loading__wrapper .loader--text {
  color: #fff;
  font-family: "Montserrat";
  font-weight: 500;
  margin-bottom: 1.4rem;
}
.loading__wrapper .loader {
  position: relative;
  width: 200px;
  height: 2px;
  background: rgba(255, 255, 255, 0.25);
}
.loading__wrapper .loader span {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: red;
  transform: scaleX(0);
  transform-origin: left;
}

@media only screen and (min-width: 800px) {
  :root {
    --card-width: 250px;
    --card-height: 400px;
  }
}

Image Slider JavaScript :

Now I have explained all the javascript functions that we have used in the Responsive touch slider. Firstly we will clear the browser console and import a gsap (GreenSock Animation Platform) powerful javascript library to enable the frontend or designers to create a timeline-based animation in this library.

GSAP Library : Click Here

Get a Left or Right button and store in the buttons JSON and fetch the card container element (cards__wrapper) and app background container element (app__bg) after that select a card info container element. if you remember that we take both buttons right or left now bind click function on both buttons the function name is swap cards and the function take a parameter of click direction.

console.clear();

const { gsap, imagesLoaded } = window;

const buttons = {
	prev: document.querySelector(".btn--left"),
	next: document.querySelector(".btn--right"),
};
const cardsContainerEl = document.querySelector(".cards__wrapper");
const appBgContainerEl = document.querySelector(".app__bg");

const cardInfosContainerEl = document.querySelector(".info__wrapper");

buttons.next.addEventListener("click", () => swapCards("right"));

buttons.prev.addEventListener("click", () => swapCards("left"));

So now discuss swap cards function when you click right or left arrow then function call with direction parameter inside the function we get a current card, previous card, next card, current image background, previous image background, next image background elements. After the call, another three functions change info,swapCardsClass, or removeCardEvents.

Now describe swapCardsClass the swapCardsClass function firstly remove the specific class that we have added on the current element and after according to direction change the previous or current card element z index value and again add all classes on the current card, previous card, next card elements and add same classes on the background image elements.

Discuss image slider changeInfo function the changeInfo function takes a direction parameter and the function take the info of the current card elements and apply gsap library timeline function when you click on right or left card arrow then apply a gsap timeline function on each info then info perform animation when you click on these buttons after that timeline call a swapInfosClass function to swap a cards Infos based on direction (right or left).

function swapCards(direction) {
	const currentCardEl = cardsContainerEl.querySelector(".current--card");
	const previousCardEl = cardsContainerEl.querySelector(".previous--card");
	const nextCardEl = cardsContainerEl.querySelector(".next--card");

	const currentBgImageEl = appBgContainerEl.querySelector(".current--image");
	const previousBgImageEl = appBgContainerEl.querySelector(".previous--image");
	const nextBgImageEl = appBgContainerEl.querySelector(".next--image");

	changeInfo(direction);
	swapCardsClass();

	removeCardEvents(currentCardEl);

	function swapCardsClass() {
		currentCardEl.classList.remove("current--card");
		previousCardEl.classList.remove("previous--card");
		nextCardEl.classList.remove("next--card");

		currentBgImageEl.classList.remove("current--image");
		previousBgImageEl.classList.remove("previous--image");
		nextBgImageEl.classList.remove("next--image");

		currentCardEl.style.zIndex = "50";
		currentBgImageEl.style.zIndex = "-2";

		if (direction === "right") {
			previousCardEl.style.zIndex = "20";
			nextCardEl.style.zIndex = "30";

			nextBgImageEl.style.zIndex = "-1";

			currentCardEl.classList.add("previous--card");
			previousCardEl.classList.add("next--card");
			nextCardEl.classList.add("current--card");

			currentBgImageEl.classList.add("previous--image");
			previousBgImageEl.classList.add("next--image");
			nextBgImageEl.classList.add("current--image");
		} else if (direction === "left") {
			previousCardEl.style.zIndex = "30";
			nextCardEl.style.zIndex = "20";

			previousBgImageEl.style.zIndex = "-1";

			currentCardEl.classList.add("next--card");
			previousCardEl.classList.add("current--card");
			nextCardEl.classList.add("previous--card");

			currentBgImageEl.classList.add("next--image");
			previousBgImageEl.classList.add("current--image");
			nextBgImageEl.classList.add("previous--image");
		}
	}
}

The updateCard function gets the current element set the current card rotation offset on specific degree means when you hover on the current card then-current card image perform a left or right 3D rotation.

function updateCard(e) {
	const card = e.currentTarget;
	const box = card.getBoundingClientRect();
	const centerPosition = {
		x: box.left + box.width / 2,
		y: box.top + box.height / 2,
	};
	let angle = Math.atan2(e.pageX - centerPosition.x, 0) * (35 / Math.PI);
	gsap.set(card, {
		"--current-card-rotation-offset": `${angle}deg`,
	});
	const currentInfoEl = cardInfosContainerEl.querySelector(".current--info");
	gsap.set(currentInfoEl, {
		rotateY: `${angle}deg`,
	});
}

Now the resetCardTransforms function track location of the current element if your cursor leaves the current active image slider card element then the gsap library sets the current card rotation to offset 0.

function resetCardTransforms(e) {
	const card = e.currentTarget;
	const currentInfoEl = cardInfosContainerEl.querySelector(".current--info");
	gsap.set(card, {
		"--current-card-rotation-offset": 0,
	});
	gsap.set(currentInfoEl, {
		rotateY: 0,
	});
}

when calling the initCardEvents function gets the current card element. if the mouse pointer on a current element then run a updateCard or when the mouse leaves the current element then run a resetCardTransforms function both functions I have already discussed.

function initCardEvents() {
	const currentCardEl = cardsContainerEl.querySelector(".current--card");
	currentCardEl.addEventListener("pointermove", updateCard);
	currentCardEl.addEventListener("pointerout", (e) => {
		resetCardTransforms(e);
	});
}

initCardEvents();

function removeCardEvents(card) {
	card.removeEventListener("pointermove", updateCard);
}

When we run a file then initialize the init function to apply the gsap library on each element that has present in the HTML.

function init() {

	let tl = gsap.timeline();

	tl.to(cardsContainerEl.children, {
		delay: 0.15,
		duration: 0.5,
		stagger: {
			ease: "power4.inOut",
			from: "right",
			amount: 0.1,
		},
		"--card-translateY-offset": "0%",
	})
		.to(cardInfosContainerEl.querySelector(".current--info").querySelectorAll(".text"), {
		delay: 0.5,
		duration: 0.4,
		stagger: 0.1,
		opacity: 1,
		translateY: 0,
	})
		.to(
		[buttons.prev, buttons.next],
		{
			duration: 0.4,
			opacity: 1,
			pointerEvents: "all",
		},
		"-=0.4"
	);
}

In this function firstly we take all images and get the length and get all children image slide current, next, previous of the cards container elements inside set the card to translate Y offset is 100vh after that iterate all images and imagesLoaded check each image instance is completely loaded. if total images are equal to loaded images then the gsap library applies timeline to loader__wrapper to set pointer event none, opacity 0, duration 0.8 after calling a init function.

const waitForImages = () => {
  const images = [...document.querySelectorAll("img")];
  const totalImages = images.length;
  let loadedImages = 0;
  const loaderEl = document.querySelector(".loader span");

  gsap.set(cardsContainerEl.children, {
    "--card-translateY-offset": "100vh",
  });
  gsap.set(cardInfosContainerEl.querySelector(".current--info").querySelectorAll(".text"), {
    translateY: "40px",
    opacity: 0,
  });
  gsap.set([buttons.prev, buttons.next], {
    pointerEvents: "none",
    opacity: "0",
  });

  images.forEach((image) => {
    imagesLoaded(image, (instance) => {
      if (instance.isComplete) {
        loadedImages++;
        let loadProgress = loadedImages / totalImages;

        gsap.to(loaderEl, {
          duration: 1,
          scaleX: loadProgress,
          backgroundColor: `hsl(${loadProgress * 120}, 100%, 50%`,
        });

        if (totalImages == loadedImages) {
          gsap.timeline()
            .to(".loading__wrapper", {
            duration: 0.8,
            opacity: 0,
            pointerEvents: "none",
          })
            .call(() => init());
        }
      }
    });
  });
};

waitForImages();

Now 3D Image Slider Responsive Touch Slider using HTML CSS & JS is completed.If you liked the article and share it with your friends and if you have any questions about the article feel free to leave me a comment and I will answer the question you have and that’s about it hopefully I will see you again in another article.

2 thoughts on “3D Image Slider Responsive Touch Slider using HTML CSS & JS

Leave a Reply

Your email address will not be published. Required fields are marked *