Learn how to build an interactive slider with card-based transitions using HTML, CSS, and JavaScript. This tutorial will guide you through creating a responsive layout with smooth sliding effects and dynamic content updates. The JavaScript code handles navigation through left and right arrows while keeping track of progress, making it perfect for displaying visual content like images or descriptions in an engaging card format. Whether you’re showcasing travel destinations or product features, this slider will enhance your website’s interactivity and user experience.
<body>
<div class="indicator"></div>
<nav>
<div>
<div class="svg-container">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418"
/>
</svg>
</div>
<div>Globe Express</div>
</div>
<div>
<div class="active">Home</div>
<div>Holidays</div>
<div>Destinations</div>
<div>Flights</div>
<div>Offers</div>
<div>Contact</div>
<div class="svg-container">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
/>
</svg>
</div>
<div class="svg-container">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M7.5 6a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM3.751 20.105a8.25 8.25 0 0116.498 0 .75.75 0 01-.437.695A18.683 18.683 0 0112 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 01-.437-.695z"
clip-rule="evenodd"
/>
</svg>
</div>
</div>
</nav>
<div id="demo"></div>
<div class="details" id="details-even">
<div class="place-box">
<div class="text">Switzerland Alps</div>
</div>
<div class="title-box-1"><div class="title-1">SAINT</div></div>
<div class="title-box-2"><div class="title-2">ANTONIEN</div></div>
<div class="desc">
Tucked away in the Switzerland Alps, Saint Antönien offers an idyllic retreat for those seeking tranquility and adventure alike. </div>
</div>
<div class="details" id="details-odd">
<div class="place-box">
<div class="text">Switzerland Alps</div>
</div>
<div class="title-box-1"><div class="title-1">SAINT </div></div>
<div class="title-box-2"><div class="title-2">ANTONIEN</div></div>
<div class="desc">
Tucked away in the Switzerland Alps, Saint Antönien offers an idyllic retreat for those seeking tranquility and adventure alike. </div>
</div>
</div>
<div class="pagination" id="pagination">
<div class="arrow arrow-left">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 19.5L8.25 12l7.5-7.5"
/>
</svg>
</div>
<div class="arrow arrow-right">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M8.25 4.5l7.5 7.5-7.5 7.5"
/>
</svg>
</div>
<div class="progress-sub-container" >
<div class="progress-sub-background" >
<div class="progress-sub-foreground" ></div>
</div>
</div>
<div class="slide-numbers" id="slide-numbers"></div>
</div>
<div class="cover" ></div>
</body>
<style>
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Oswald:wght@500&display=swap");
body {
margin: 0;
background-color: #1a1a1a;
color: #FFFFFFDD;
position: relative;
overflow: hidden;
font-family: "Inter", sans-serif;
}
.card {
position: absolute;
left: 0;
top: 0;
background-position: center;
background-size: cover;
box-shadow: 6px 6px 10px 2px rgba(0, 0, 0, 0.6);
}
#btn {
position: absolute;
top: 690px;
left: 16px;
z-index: 99;
}
.card-content {
position: absolute;
left: 0;
top: 0;
color: #FFFFFFDD;
padding-left: 16px;
}
.content-place {
margin-top: 6px;
font-size: 13px;
font-weight: 500;
}
.content-title-1,
.content-title-2 {
font-weight: 600;
font-size: 20px;
font-family: "Oswald", sans-serif;
}
.content-start {
width: 30px;
height: 5px;
border-radius: 99px;
background-color: #FFFFFFDD;
}
.details {
z-index: 22;
position: absolute;
top: 240px;
left: 60px;
}
.details .place-box {
height: 46px;
overflow: hidden;
}
.details .place-box .text {
padding-top: 16px;
font-size: 20px;
position: relative;
}
.details .place-box .text:before {
top: 0;
left: 0;
position: absolute;
content: "";
width: 30px;
height: 4px;
border-radius: 99px;
background-color: white;
}
.details .title-1,
.details .title-2 {
font-weight: 600;
font-size: 72px;
font-family: "Oswald", sans-serif;
}
.details .title-box-1,
.details .title-box-2 {
margin-top: 2px;
height: 100px;
overflow: hidden;
}
.details > .desc {
margin-top: 16px;
width: 350px;
}
.details > .cta {
width: 500px;
margin-top: 24px;
display: flex;
align-items: center;
}
.details > .cta > .bookmark {
border: none;
background-color: #ecad29;
width: 36px;
height: 36px;
border-radius: 99px;
color: white;
display: grid;
place-items: center;
}
.details > .cta > .bookmark svg {
width: 20px;
height: 20px;
}
.details > .cta > .discover {
border: 1px solid #ffffff;
background-color: transparent;
height: 36px;
border-radius: 99px;
color: #ffffff;
padding: 4px 24px;
font-size: 12px;
margin-left: 16px;
text-transform: uppercase;
}
nav {
position: fixed;
left: 0;
top: 0;
right: 0;
z-index: 50;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 36px;
font-weight: 500;
}
nav svg {
width: 20px;
height: 20px;
}
nav .svg-container {
width: 20px;
height: 20px;
}
nav > div {
display: inline-flex;
align-items: center;
text-transform: uppercase;
font-size: 14px;
}
nav > div:first-child {
gap: 10px;
}
nav > div:last-child {
gap: 24px;
}
nav > div:last-child > .active {
position: relative;
}
nav > div:last-child > .active:after {
bottom: -8px;
left: 0;
right: 0;
position: absolute;
content: "";
height: 3px;
border-radius: 99px;
background-color: #ecad29;
}
.indicator {
position: fixed;
left: 0;
right: 0;
top: 0;
height: 5px;
z-index: 60;
background-color: #ecad29;
}
.pagination {
position: absolute;
left: 0px;
top: 0px;
display: inline-flex;
}
.pagination > .arrow {
z-index: 60;
width: 50px;
height: 50px;
border-radius: 999px;
border: 2px solid #ffffff55;
display: grid;
place-items: center;
}
.pagination > .arrow:nth-child(2) {
margin-left: 20px;
}
.pagination > .arrow svg {
width: 24px;
height: 24px;
stroke-width: 2;
color: #ffffff99;
}
.pagination .progress-sub-container {
margin-left: 24px;
z-index: 60;
width: 500px;
height: 50px;
display: flex;
align-items: center;
}
.pagination .progress-sub-background {
width: 500px;
height: 3px;
background-color: #ffffff33;
}
.pagination .progress-sub-foreground {
height: 3px;
background-color: #ecad29;
}
.pagination .slide-numbers {
width: 50px;
height: 50px;
overflow: hidden;
z-index: 60;
position: relative;
}
.pagination .slide-numbers .item {
width: 50px;
height: 50px;
position: absolute;
color: white;
top: 0;
left: 0;
display: grid;
place-items: center;
font-size: 32px;
font-weight: bold;
}
.cover {
position: absolute;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background-color: #fff;
z-index: 100;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script>
const data = [
{
place:'Switzerland Alps',
title:'SAINT',
title2:'ANTONIEN',
description:'Tucked away in the Switzerland Alps, Saint Antönien offers an idyllic retreat for those seeking tranquility and adventure alike. ',
image:'https://assets.codepen.io/3685267/timed-cards-1.jpg'
},
{
place:'Japan Alps',
title:'NANGANO',
title2:'PREFECTURE',
description:'Tucked away in the Switzerland Alps, Saint Antönien offers an idyllic retreat for those seeking tranquility and adventure alike. ',
image:'https://assets.codepen.io/3685267/timed-cards-2.jpg'
},
{
place:'Sahara Desert - Morocco',
title:'MARRAKECH',
title2:'MEROUGA',
description:'Tucked away in the Switzerland Alps, Saint Antönien offers an idyllic retreat for those seeking tranquility and adventure alike. ',
image:'https://assets.codepen.io/3685267/timed-cards-3.jpg'
},
{
place:'Sierra Nevada - USA',
title:'YOSEMITE',
title2:'NATIONAL PARAK',
description:'Tucked away in the Switzerland Alps, Saint Antönien offers an idyllic retreat for those seeking tranquility and adventure alike. ',
image:'https://assets.codepen.io/3685267/timed-cards-4.jpg'
},
{
place:'Tarifa - Spain',
title:'LOS LANCES',
title2:'BEACH',
description:'Tucked away in the Switzerland Alps, Saint Antönien offers an idyllic retreat for those seeking tranquility and adventure alike. ',
image:'https://assets.codepen.io/3685267/timed-cards-5.jpg'
},
{
place:'Cappadocia - Turkey',
title:'Göreme',
title2:'Valley',
description:'Tucked away in the Switzerland Alps, Saint Antönien offers an idyllic retreat for those seeking tranquility and adventure alike. ',
image:'https://assets.codepen.io/3685267/timed-cards-6.jpg'
},
]
const _ = (id)=>document.getElementById(id)
const cards = data.map((i, index)=>`<div class="card" id="card${index}" style="background-image:url(${i.image})" ></div>`).join('')
const cardContents = data.map((i, index)=>`<div class="card-content" id="card-content-${index}">
<div class="content-start"></div>
<div class="content-place">${i.place}</div>
<div class="content-title-1">${i.title}</div>
<div class="content-title-2">${i.title2}</div>
</div>`).join('')
const sildeNumbers = data.map((_, index)=>`<div class="item" id="slide-item-${index}" >${index+1}</div>`).join('')
_('demo').innerHTML = cards + cardContents
_('slide-numbers').innerHTML = sildeNumbers
const range = (n) =>
Array(n)
.fill(0)
.map((i, j) => i + j);
const set = gsap.set;
function getCard(index) {
return `#card${index}`;
}
function getCardContent(index) {
return `#card-content-${index}`;
}
function getSliderItem(index) {
return `#slide-item-${index}`;
}
function animate(target, duration, properties) {
return new Promise((resolve) => {
gsap.to(target, {
...properties,
duration: duration,
onComplete: resolve,
});
});
}
let order = [0, 1, 2, 3, 4, 5];
let detailsEven = true;
let offsetTop = 200;
let offsetLeft = 700;
let cardWidth = 200;
let cardHeight = 300;
let gap = 40;
let numberSize = 50;
const ease = "sine.inOut";
function init() {
const [active, ...rest] = order;
const detailsActive = detailsEven ? "#details-even" : "#details-odd";
const detailsInactive = detailsEven ? "#details-odd" : "#details-even";
const { innerHeight: height, innerWidth: width } = window;
offsetTop = height - 430;
offsetLeft = width - 830;
gsap.set("#pagination", {
top: offsetTop + 330,
left: offsetLeft,
y: 200,
opacity: 0,
zIndex: 60,
});
gsap.set("nav", { y: -200, opacity: 0 });
gsap.set(getCard(active), {
x: 0,
y: 0,
width: window.innerWidth,
height: window.innerHeight,
});
gsap.set(getCardContent(active), { x: 0, y: 0, opacity: 0 });
gsap.set(detailsActive, { opacity: 0, zIndex: 22, x: -200 });
gsap.set(detailsInactive, { opacity: 0, zIndex: 12 });
gsap.set(`${detailsInactive} .text`, { y: 100 });
gsap.set(`${detailsInactive} .title-1`, { y: 100 });
gsap.set(`${detailsInactive} .title-2`, { y: 100 });
gsap.set(`${detailsInactive} .desc`, { y: 50 });
gsap.set(`${detailsInactive} .cta`, { y: 60 });
gsap.set(".progress-sub-foreground", {
width: 500 * (1 / order.length) * (active + 1),
});
rest.forEach((i, index) => {
gsap.set(getCard(i), {
x: offsetLeft + 400 + index * (cardWidth + gap),
y: offsetTop,
width: cardWidth,
height: cardHeight,
zIndex: 30,
borderRadius: 10,
});
gsap.set(getCardContent(i), {
x: offsetLeft + 400 + index * (cardWidth + gap),
zIndex: 40,
y: offsetTop + cardHeight - 100,
});
gsap.set(getSliderItem(i), { x: (index + 1) * numberSize });
});
gsap.set(".indicator", { x: -window.innerWidth });
const startDelay = 0.6;
gsap.to(".cover", {
x: width + 400,
delay: 0.5,
ease,
onComplete: () => {
setTimeout(() => {
loop();
}, 500);
},
});
rest.forEach((i, index) => {
gsap.to(getCard(i), {
x: offsetLeft + index * (cardWidth + gap),
zIndex: 30,
delay: 0.05 * index,
ease,
delay: startDelay,
});
gsap.to(getCardContent(i), {
x: offsetLeft + index * (cardWidth + gap),
zIndex: 40,
delay: 0.05 * index,
ease,
delay: startDelay,
});
});
gsap.to("#pagination", { y: 0, opacity: 1, ease, delay: startDelay });
gsap.to("nav", { y: 0, opacity: 1, ease, delay: startDelay });
gsap.to(detailsActive, { opacity: 1, x: 0, ease, delay: startDelay });
}
let clicks = 0;
function step() {
return new Promise((resolve) => {
order.push(order.shift());
detailsEven = !detailsEven;
const detailsActive = detailsEven ? "#details-even" : "#details-odd";
const detailsInactive = detailsEven ? "#details-odd" : "#details-even";
document.querySelector(`${detailsActive} .place-box .text`).textContent =
data[order[0]].place;
document.querySelector(`${detailsActive} .title-1`).textContent =
data[order[0]].title;
document.querySelector(`${detailsActive} .title-2`).textContent =
data[order[0]].title2;
document.querySelector(`${detailsActive} .desc`).textContent =
data[order[0]].description;
gsap.set(detailsActive, { zIndex: 22 });
gsap.to(detailsActive, { opacity: 1, delay: 0.4, ease });
gsap.to(`${detailsActive} .text`, {
y: 0,
delay: 0.1,
duration: 0.7,
ease,
});
gsap.to(`${detailsActive} .title-1`, {
y: 0,
delay: 0.15,
duration: 0.7,
ease,
});
gsap.to(`${detailsActive} .title-2`, {
y: 0,
delay: 0.15,
duration: 0.7,
ease,
});
gsap.to(`${detailsActive} .desc`, {
y: 0,
delay: 0.3,
duration: 0.4,
ease,
});
gsap.to(`${detailsActive} .cta`, {
y: 0,
delay: 0.35,
duration: 0.4,
onComplete: resolve,
ease,
});
gsap.set(detailsInactive, { zIndex: 12 });
const [active, ...rest] = order;
const prv = rest[rest.length - 1];
gsap.set(getCard(prv), { zIndex: 10 });
gsap.set(getCard(active), { zIndex: 20 });
gsap.to(getCard(prv), { scale: 1.5, ease });
gsap.to(getCardContent(active), {
y: offsetTop + cardHeight - 10,
opacity: 0,
duration: 0.3,
ease,
});
gsap.to(getSliderItem(active), { x: 0, ease });
gsap.to(getSliderItem(prv), { x: -numberSize, ease });
gsap.to(".progress-sub-foreground", {
width: 500 * (1 / order.length) * (active + 1),
ease,
});
gsap.to(getCard(active), {
x: 0,
y: 0,
ease,
width: window.innerWidth,
height: window.innerHeight,
borderRadius: 0,
onComplete: () => {
const xNew = offsetLeft + (rest.length - 1) * (cardWidth + gap);
gsap.set(getCard(prv), {
x: xNew,
y: offsetTop,
width: cardWidth,
height: cardHeight,
zIndex: 30,
borderRadius: 10,
scale: 1,
});
gsap.set(getCardContent(prv), {
x: xNew,
y: offsetTop + cardHeight - 100,
opacity: 1,
zIndex: 40,
});
gsap.set(getSliderItem(prv), { x: rest.length * numberSize });
gsap.set(detailsInactive, { opacity: 0 });
gsap.set(`${detailsInactive} .text`, { y: 100 });
gsap.set(`${detailsInactive} .title-1`, { y: 100 });
gsap.set(`${detailsInactive} .title-2`, { y: 100 });
gsap.set(`${detailsInactive} .desc`, { y: 50 });
gsap.set(`${detailsInactive} .cta`, { y: 60 });
clicks -= 1;
if (clicks > 0) {
step();
}
},
});
rest.forEach((i, index) => {
if (i !== prv) {
const xNew = offsetLeft + index * (cardWidth + gap);
gsap.set(getCard(i), { zIndex: 30 });
gsap.to(getCard(i), {
x: xNew,
y: offsetTop,
width: cardWidth,
height: cardHeight,
ease,
delay: 0.1 * (index + 1),
});
gsap.to(getCardContent(i), {
x: xNew,
y: offsetTop + cardHeight - 100,
opacity: 1,
zIndex: 40,
ease,
delay: 0.1 * (index + 1),
});
gsap.to(getSliderItem(i), { x: (index + 1) * numberSize, ease });
}
});
});
}
async function loop() {
await animate(".indicator", 2, { x: 0 });
await animate(".indicator", 0.8, { x: window.innerWidth, delay: 0.3 });
set(".indicator", { x: -window.innerWidth });
await step();
loop();
}
async function loadImage(src) {
return new Promise((resolve, reject) => {
let img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
async function loadImages() {
const promises = data.map(({ image }) => loadImage(image));
return Promise.all(promises);
}
async function start() {
try {
await loadImages();
init();
} catch (error) {
console.error("One or more images failed to load", error);
}
}
start()
</script>
Credit: https://codepen.io/dilums/pen/NWodZMd