Learn how to build an interactive carousel slider using GSAP animations, HTML, CSS, and JavaScript. Follow our step-by-step tutorial for stunning web animations.
Table of Contents
In this tutorial, we'll dive into the exciting world of web animation and learn how to create a stunning carousel slider using GSAP (GreenSock Animation Platform), along with HTML, CSS, and JavaScript. Carousels are a popular feature on websites, allowing you to showcase multiple images or content in an interactive and visually appealing way. By leveraging the power of GSAP, we can take our carousel to the next level with smooth animations and dynamic effects.
Throughout this guide, we'll walk you through each step of the process, from setting up the HTML structure to adding interactivity and animations with GSAP. Whether you're a beginner or an experienced developer looking to level up your skills, this tutorial will equip you with the knowledge and techniques needed to create impressive carousel sliders for your web projects.
So, let's roll up our sleeves and get started on building a GSAP carousel slider that will captivate your audience and elevate your website's user experience!
Source Code
Step 1 (HTML Code):
To begin creating our GSAP carousel slider, we first need to set up the HTML structure. This structure will define the layout and elements of our carousel.
Let's break down the structure and functionality:
1. <!DOCTYPE html>: This declaration specifies the HTML version being used.
2. <html lang="en">: The root element of the HTML document, specifying the language as English.
3. <head>: This section contains meta-information about the document such as character encoding, viewport settings, and links to external resources like stylesheets and scripts.
- <meta charset="UTF-8">: Sets the character encoding to UTF-8.
- <meta http-equiv="X-UA-Compatible" content="IE=edge">: Instructs Internet Explorer to use the latest rendering engine available.
- <meta name="viewport" content="width=device-width, initial-scale=1.0">: Defines the viewport for responsive design.
- <title>GSAP Carousel Slider</title>: Sets the title of the webpage.
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/aj...in.css">: Links to a stylesheet for Font Awesome icons.
- <link rel="stylesheet" href="https://fonts.google..splay=swap" rel="stylesheet">: Links to a Google Fonts stylesheet for the Montserrat font.
- <link rel="stylesheet" href="styles.css">: Links to an external stylesheet named styles.css.
4. <body>: The main content of the webpage.
- <div class="app">: Main container for the carousel slider.
- .cardList: Container for the carousel cards.
- .cardList__btn: Buttons for navigating left and right within the carousel.
- .icon: Container for the SVG icon.
- <svg>: SVG element for displaying an arrow icon.
- <use xlink:href="#arrow-left"></use>: Use the symbol with the id arrow-left defined in the <svg class="icons"> section.
- .cards__wrapper: Container for the carousel cards.
- .card: Individual cards in the carousel.
- .card__image: Container for the card image.
- <img>: Image element displaying the card's image.
- .infoList: Container for information related to each card.
- .info: Individual information blocks.
- <h1>, <h4>, <p>: Headings and paragraphs containing information about each card.
- .app__bg: Container for background images associated with the cards.
- .app__bg__image: Individual background image elements.
- <img>: Background images associated with each card.
- <div class="loading__wrapper">: Container for a loading animation.
- .loader--text: Text indicating that the page is loading.
- .loader: Container for the loading animation.
- <svg class="icons">: Defines SVG icons used in the page.
- <symbol>: Symbols representing left and right arrows.
5. External scripts:
- <script src="https://unpkg.com/imagesl...js"></script>: Loads the ImagesLoaded library.
- <script src="https://cdnjs.cloudflar..gsap.min.js"></script>: Loads the GSAP library.
- <script src="script.js"></script>: Links to an external JavaScript file named script.js for custom scripting related to the carousel functionality.
Step 2 (CSS Code):
Now that we have set up the HTML structure for our carousel slider, let's add some styles to make it visually appealing. Let's break down the code section by section:
1. Importing a Font and Setting Variables:
- The code starts by importing a font from Google Fonts called "Montserrat" with various weights.
- Then, it defines variables in the :root block. These variables hold values used throughout the stylesheet, making it easier to maintain consistency. They include card dimensions, transition durations, and easing functions.
2. Global Styles:
- The * selector applies styles to all elements. It sets the box-sizing property to ensure consistent width calculations and resets margins and paddings to zero.
- The body styles define full width and height, center alignment of content, a dark background with some transparency, and hides any overflow.
- Button styles remove default borders and backgrounds and set a pointer cursor on hover.
3. App Structure:
- The .app class styles the main application container. It positions elements relatively, uses flexbox for centering, and defines a dark blurred background image with a black overlay.
- The styles for .app__bg__image define how the background image is positioned, scaled, and transitioned on various states (current, previous, next).
4. Card List and Buttons:
- The .cardList class styles the container holding the cards. It sets its width based on the card width variable.
- Styles for .cardList__btn define buttons for navigating between cards with positioning and size.
5. Card Styles:
- The .card class styles the individual cards. It uses variables for positioning, rotation, scale, and opacity based on their state (current, previous, next). It also defines a black overlay that fades out for non-current cards.
- Styles within .card define how the card image is positioned and displayed.
6. Card Information:
- The .infoList class styles a container for card information, similar to the card list container.
- The .info class styles individual information sections within a card. It includes styles for margins, positioning, and transitions.
- Text styles within .info define font family (set to the imported "Montserrat"), size based on a variable, color, and formatting for specific content types like names and locations.
7. Loading Screen:
- Styles for the .loading__wrapper class define a fullscreen overlay displayed while loading with a centered text message and a progress bar.
8. Media Queries:
- The media query applies styles specifically for screens wider than 800px. In this case, it increases the card dimensions.
9. Footer Links:
- Styles for the .support class define a footer section with positioned links and hover effects.
@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;
}
}
.support {
position: absolute;
right: 10px;
bottom: 10px;
padding: 10px;
display: flex;
}
.support a {
margin: 0 10px;
color: #fff;
font-size: 1.8rem;
backface-visibility: hidden;
transition: all 150ms ease;
}
.support a:hover {
transform: scale(1.1);
}
Step 3 (JavaScript Code):
Now, let's bring our carousel slider to life by adding interactivity and animations using GSAP (GreenSock Animation Platform). GSAP provides powerful tools for creating smooth animations and transitions, making our carousel slider more dynamic and engaging. Let's break down the key parts of the code:
1. Variable Declarations:
- The const keyword is used to declare variables gsap and imagesLoaded from the window object.
- Several DOM elements and button references are stored in variables.
2. Event Listeners:
- Event listeners are attached to the "next" and "previous" buttons, triggering the swapCards function with the respective direction parameter.
3. swapCards Function:
- This function is responsible for swapping the current, previous, and next cards based on the direction parameter.
- It also swaps background images accordingly.
- The changeInfo function is called within swapCards to handle the swapping of information associated with the cards.
4. changeInfo Function:
- This function animates the change in information associated with the cards.
- It utilizes GSAP (GreenSock Animation Platform) for animation effects.
5. Card Transformation Functions:
- updateCard and resetCardTransforms functions handle the rotation and resetting of card transformations respectively.
6. Initialization Functions:
- initCardEvents set up event listeners for the current card.
- init initializes the animation timeline using GSAP.
7. waitForImages Function:
- This function waits for all images to be loaded before initializing the interface.
- It updates a loader element's scale and background color to reflect the loading progress.
- Once all images are loaded, it fades out the loading screen and triggers the init function to start the interface animation.
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"));
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");
}
}
}
function changeInfo(direction) {
let currentInfoEl = cardInfosContainerEl.querySelector(".current--info");
let previousInfoEl = cardInfosContainerEl.querySelector(".previous--info");
let nextInfoEl = cardInfosContainerEl.querySelector(".next--info");
gsap.timeline()
.to([buttons.prev, buttons.next], {
duration: 0.2,
opacity: 0.5,
pointerEvents: "none",
})
.to(
currentInfoEl.querySelectorAll(".text"),
{
duration: 0.4,
stagger: 0.1,
translateY: "-120px",
opacity: 0,
},
"-="
)
.call(() => {
swapInfosClass(direction);
})
.call(() => initCardEvents())
.fromTo(
direction === "right"
? nextInfoEl.querySelectorAll(".text")
: previousInfoEl.querySelectorAll(".text"),
{
opacity: 0,
translateY: "40px",
},
{
duration: 0.4,
stagger: 0.1,
translateY: "0px",
opacity: 1,
}
)
.to([buttons.prev, buttons.next], {
duration: 0.2,
opacity: 1,
pointerEvents: "all",
});
function swapInfosClass() {
currentInfoEl.classList.remove("current--info");
previousInfoEl.classList.remove("previous--info");
nextInfoEl.classList.remove("next--info");
if (direction === "right") {
currentInfoEl.classList.add("previous--info");
nextInfoEl.classList.add("current--info");
previousInfoEl.classList.add("next--info");
} else if (direction === "left") {
currentInfoEl.classList.add("next--info");
nextInfoEl.classList.add("previous--info");
previousInfoEl.classList.add("current--info");
}
}
}
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`,
});
}
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,
});
}
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);
}
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"
);
}
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();
Final Output:
Conclusion:
Congratulations! You've now mastered the art of creating a GSAP carousel slider from scratch using HTML, CSS, and JavaScript. Throughout this tutorial, we've covered the essential steps, from setting up the HTML structure to adding smooth animations with GSAP.
By incorporating GSAP into your carousel slider, you've learned how to take your web animations to the next level, creating a dynamic and engaging user experience. Whether you're showcasing products, portfolio items, or blog posts, a well-designed carousel slider can make a significant impact on your website's aesthetic appeal and usability.
We hope you found this tutorial helpful and inspiring. Now, armed with your newfound knowledge of GSAP and carousel sliders, go forth and create stunning web experiences that will leave your audience impressed. Happy coding!
Code by: Sikriti Dakua
That’s a wrap!
I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.
Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee
And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!
Thanks!
Faraz 😊