Created
November 21, 2024 16:07
-
-
Save denis23x/4328e4fbd1fe6326b29ad5e1806a22c1 to your computer and use it in GitHub Desktop.
expanding-cards
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Expanding</title> | |
<link href="https://cdn.bootcss.com/font-awesome/4.7.0/fonts/fontawesome-webfont.svg"> | |
<link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"> </script> | |
</head> | |
<body> | |
<div class="expanding"> | |
<div class="indicator"></div> | |
<div id="demo"></div> | |
<div class="card"> | |
<div class="details" id="details-even"> | |
<div class="place-box"> | |
<div class="text">paintballing packages</div> | |
</div> | |
<div class="title-box-1"><div class="title-1">Commando</div></div> | |
<div class="title-box-2"><div class="title-2">£30</div></div> | |
<div class="desc"> | |
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. | |
</div> | |
<div class="cta"> | |
<button class="discover" href="#">BOOK NOW</button> | |
</div> | |
</div> | |
<div class="details" id="details-odd"> | |
<div class="place-box"> | |
<div class="text">paintballing packages</div> | |
</div> | |
<div class="title-box-1"><div class="title-1">Commando </div></div> | |
<div class="title-box-2"><div class="title-2">£30</div></div> | |
<div class="desc"> | |
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. </div> | |
<div class="cta"> | |
<button class="discover" href="#">BOOK NOW</button> | |
</div> | |
</div> | |
</div> | |
<div class="pagination" id="pagination"> | |
<button href="#" class="previous arrow arrow-left" onclick="activate()"> | |
<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> | |
</button> | |
<button href="#" class="next arrow arrow-right" onclick="activate()"> | |
<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> | |
</button> | |
<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> | |
</div> | |
<style> | |
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;700;800&display=swap'); | |
.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; | |
margin-top: 1%; | |
} | |
.content-place { | |
display: none; | |
} | |
.content-title-1, | |
.content-title-2 { | |
font-weight: 600; | |
font-size: 20px; | |
font-family: "Poppins", sans-serif; | |
} | |
.content-start { | |
width: 30px; | |
height: 5px; | |
border-radius: 99px; | |
background-color: #ffffffdd; | |
margin-bottom: 5%; | |
} | |
.details { | |
font-family: "Poppins", sans-serif; | |
color:white; | |
z-index: 22; | |
position: absolute; | |
top: 240px; | |
left: 60px; | |
.place-box { | |
height: 46px; | |
overflow: hidden; | |
.text { | |
padding-top: 16px; | |
font-size: 20px; | |
&:before { | |
top: 0; | |
left: 0; | |
position: absolute; | |
content: ""; | |
width: 30px; | |
height: 4px; | |
border-radius: 99px; | |
background-color: white; | |
} | |
} | |
} | |
.title-1, | |
.title-2 { | |
color:white; | |
font-weight: 600; | |
font-size: 72px; | |
font-family: "Poppins", sans-serif; | |
margin-top: 2px; | |
height: 100px; | |
overflow: hidden; | |
} | |
> .desc { | |
font-family: "Poppins", sans-serif; | |
color:white; | |
margin-top: 16px; | |
width: 500px; | |
} | |
> .cta { | |
width: 500px; | |
margin-top: 24px; | |
margin-left: -4%; | |
display: flex; | |
align-items: center; | |
> .bookmark { | |
border: none; | |
background-color: #EF9821; | |
width: 40px; | |
height: 40px; | |
border-radius: 99px; | |
color: white; | |
display: grid; | |
place-items: center; | |
svg { | |
width: 20px; | |
height: 20px; | |
} | |
} | |
> .discover { | |
border: 1px solid #ffffff; | |
background-color: transparent; | |
height: 60px; | |
border-radius: 99px; | |
color: #ffffff; | |
padding: 4px 24px; | |
font-size: 18px; | |
margin-left: 16px; | |
text-transform: uppercase; | |
font-family: "Poppins", sans-serif; | |
font-weight: 500; | |
} | |
> .discover:hover { | |
background-color: #ef9921; | |
border: 1px solid #ef9921; | |
transition-timing-function: linear; | |
transition: 0.5s; | |
} | |
} | |
} | |
.svg-container { | |
width: 20px; | |
height: 20px; | |
} | |
.div { | |
display: inline-flex; | |
align-items: center; | |
text-transform: uppercase; | |
font-size: 14px; | |
&:first-child { | |
gap: 10px; | |
} | |
&:last-child { | |
gap: 24px; | |
> .active { | |
position: relative; | |
&:after { | |
bottom: -8px; | |
left: 0; | |
right: 0; | |
position: absolute; | |
content: ""; | |
height: 3px; | |
border-radius: 99px; | |
background-color: #ef9921; | |
} | |
} | |
} | |
} | |
.indicator { | |
position: fixed; | |
left: 0; | |
right: 0; | |
top: 0; | |
height: 5px; | |
z-index: 60; | |
background-color: #ef9921; | |
} | |
.pagination { | |
position: absolute; | |
left: 0px; | |
top: 0px; | |
display: inline-flex; | |
> .arrow { | |
z-index: 60; | |
width: 50px; | |
height: 50px; | |
border-radius: 999px; | |
background-color: #ecae2900; | |
border: 2px solid #ffffff; | |
display: grid; | |
place-items: center; | |
&:nth-child(2) { | |
margin-left: 20px; | |
} | |
svg { | |
width: 24px; | |
height: 24px; | |
stroke-width: 2; | |
color: #ffffff; | |
} | |
} | |
.arrow-left:hover { | |
background-color: #ef9921; | |
border: 2px solid #ef9921; | |
transition-timing-function: linear; | |
transition: 0.5s; | |
} | |
.arrow-right:hover { | |
background-color: #ef9921; | |
border: 2px solid #ef9921; | |
transition-timing-function: linear; | |
transition: 0.5s; | |
} | |
.progress-sub-container { | |
margin-left: 24px; | |
z-index: 60; | |
width: 295px; | |
height: 50px; | |
display: flex; | |
align-items: center; | |
.progress-sub-background { | |
width: 300px; | |
height: 3px; | |
background-color: #ffffff33; | |
.progress-sub-foreground { | |
height: 3px; | |
background-color: #ef9921; | |
} | |
} | |
} | |
.slide-numbers { | |
display: none; | |
width: 50px; | |
height: 50px; | |
overflow: hidden; | |
z-index: 60; | |
position: relative; | |
.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> | |
const data = [ | |
{ | |
place:'paintballing packages', | |
title:'Commando', | |
title2:'£30', | |
description:'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.', | |
image:'https://rednalcombatltd.wpcomstaging.com/wp-content/uploads/2024/11/DSC_4742.jpg' | |
}, | |
{ | |
place:'paintballing packages', | |
title:'Desert Storm', | |
title2:'£38', | |
description:'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.', | |
image:'https://rednalcombatltd.wpcomstaging.com/wp-content/uploads/2024/11/438706688_940363681425594_763346074187593378_n.jpg' | |
}, | |
{ | |
place:'paintballing packages', | |
title:'Ultimate Combat', | |
title2:'£55', | |
description:'Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry\'s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.', | |
image:'https://rednalcombatltd.wpcomstaging.com/wp-content/uploads/2024/11/ultimate.png' | |
}, | |
] | |
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 slider = document.querySelector('.card'); | |
function activate(e) { | |
const newIndex = e ? Number(e.target.id.replace('card', '')) : (orderActive + 1 === 3 ? 0 : orderActive + 1); | |
step(newIndex); | |
} | |
const sildeNumbers = data.map((_, index)=>`<div class="item" id="slide-item-${index}" >${index+1}</div>`).join('') | |
_('demo').innerHTML = cards + cardContents | |
_('slide-numbers').innerHTML = sildeNumbers | |
document.querySelectorAll('#demo .card').forEach(el => { | |
el.addEventListener('click',activate,false); | |
}) | |
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]; | |
let orderActive = 0; | |
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 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, duration: 1.5, ease: 'power2.inOut' }); | |
gsap.set(detailsInactive, { zIndex: 20 }); | |
gsap.to(detailsInactive, { opacity: 0, duration: 1.5, ease: 'power2.inOut' }); | |
resolve(); | |
}); | |
} | |
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 - 600; | |
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 = 1; | |
gsap.to(".cover", { | |
x: width + 400, | |
delay: 1, | |
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(detailsActive, { opacity: 1, x: 0, ease, delay: startDelay }); | |
} | |
let clicks = 0; | |
function step(zz) { | |
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.3, | |
duration: 0.7, | |
ease, | |
}); | |
gsap.to(`${detailsActive} .title-1`, { | |
y: 0, | |
delay: 0.3, | |
duration: 0.7, | |
ease, | |
}); | |
gsap.to(`${detailsActive} .title-2`, { | |
y: 0, | |
delay: 0.3, | |
duration: 0.7, | |
ease, | |
}); | |
gsap.to(`${detailsActive} .desc`, { | |
y: 0, | |
delay: 0.3, | |
duration: 0.7, | |
ease, | |
}); | |
gsap.to(`${detailsActive} .cta`, { | |
y: 0, | |
delay: 0.3, | |
duration: 0.7, | |
onComplete: resolve, | |
ease, | |
}); | |
gsap.set(detailsInactive, { zIndex: 12 }); | |
const [xx, ...rest] = order; | |
const active = zz || xx; | |
orderActive = active; | |
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.15, | |
ease, | |
}); | |
gsap.to(getSliderItem(active), { x: 0, ease }); | |
gsap.to(getSliderItem(prv), { x: -numberSize, ease }); | |
gsap.to(".progress-sub-foreground", { | |
width: 300 * (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.2 * (index + 1), | |
}); | |
gsap.to(getCardContent(i), { | |
x: xNew, | |
y: offsetTop + cardHeight - 100, | |
opacity: 1, | |
zIndex: 40, | |
ease, | |
delay: 0.2 * (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: 4 }); | |
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> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment