Created
August 12, 2022 05:39
-
-
Save alamindevms/a899b76dba1f5f5beb0e51bfcc12351a to your computer and use it in GitHub Desktop.
Carousel with gsap animation, tailwindcss
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
<section class="relative overflow-hidden h-[250px] lg:h-[650px] bg-cover w-full bg-bottom"> | |
<div class="h-full w-full"> | |
<div class="wrapper"> | |
<div id="slider"> | |
<div class="slide"> | |
<div class="slide-wrapper"> | |
<div class="img-wrapper"> | |
<img src="https://wallpapercave.com/wp/wp1808936.jpg" alt=""> | |
</div> | |
<div class="title-wrapper"> | |
<div class="inner-wrapper"> | |
<div class="slide-title">captain america</div> | |
<div class="slide-subtitle">Recipient of the Super-Soldier serum, World War II hero Steve Rogers fights for American ideals as one of the world’s mightiest heroes and the leader of the Avengers.</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="slide"> | |
<div class="slide-wrapper"> | |
<div class="img-wrapper"> | |
<img src="https://iliketowastemytime.com/sites/default/files/iron-man-3-hd-wallpaper-iltwmt.jpg" alt=""> | |
</div> | |
<div class="title-wrapper"> | |
<div class="inner-wrapper"> | |
<div class="slide-title">iron man</div> | |
<div class="slide-subtitle">Genius. Billionaire. Playboy. Philanthropist. Tony Stark's confidence is only matched by his high-flying abilities as the hero called Iron Man.</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="slide"> | |
<div class="slide-wrapper"> | |
<div class="img-wrapper"> | |
<img src="https://www.wallpaperup.com/uploads/wallpapers/2014/04/08/326808/bc9eede125dfabdda7f6649e28c08312.jpg" alt=""> | |
</div> | |
<div class="title-wrapper"> | |
<div class="inner-wrapper"> | |
<div class="slide-title">spider-man</div> | |
<div class="slide-subtitle">Bitten by a radioactive spider, Peter Parker’s arachnid abilities give him amazing powers he uses to help others, while his personal life continues to offer plenty of obstacles.</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="slide"> | |
<div class="slide-wrapper"> | |
<div class="img-wrapper"> | |
<img src="https://images5.alphacoders.com/698/thumb-1920-698123.jpg" alt=""> | |
</div> | |
<div class="title-wrapper"> | |
<div class="inner-wrapper"> | |
<div class="slide-title">doctor strange</div> | |
<div class="slide-subtitle">Formerly a renowned surgeon, Doctor Stephen Strange now serves as the Sorcerer Supreme—Earth’s foremost protector against magical and mystical threats.</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="slide"> | |
<div class="slide-wrapper"> | |
<div class="img-wrapper"> | |
<img src="https://www.hdwallpapersfreedownload.com/uploads/large/super-heroes/2013-thor-the-dark-world.jpg" alt=""> | |
</div> | |
<div class="title-wrapper"> | |
<div class="inner-wrapper"> | |
<div class="slide-title">thor</div> | |
<div class="slide-subtitle">The son of Odin uses his mighty abilities as the God of Thunder to protect his home Asgard and planet Earth alike.</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<nav id="navigation"> | |
<div class="bullet"></div> | |
<div class="bullet"></div> | |
<div class="bullet"></div> | |
<div class="bullet"></div> | |
<div class="bullet"></div> | |
</nav> | |
</div> | |
</div> | |
</div> | |
</section> |
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
// TODO: Autoplay | |
// TODO: Clean up the code | |
// TODO: Add more control options | |
class Utility { | |
debounce(func, wait, immediate) { | |
var timeout; | |
return function () { | |
var context = this, | |
args = arguments; | |
var later = function () { | |
timeout = null; | |
if (!immediate) func.apply(context, args); | |
}; | |
var callNow = immediate && !timeout; | |
clearTimeout(timeout); | |
timeout = setTimeout(later, wait); | |
if (callNow) func.apply(context, args); | |
}; | |
} | |
getMousePosition(e) { | |
let posX = 0; | |
let posY = 0; | |
if (!e) { | |
e = window.event; | |
} | |
if (e.pageX || e.pageY) { | |
posX = e.pageX; | |
posY = e.pageY; | |
} else if (e.clientX || e.clientY) { | |
posX = | |
e.clientX + | |
document.body.scrollLeft + | |
document.documentElement.scrollLeft; | |
posY = | |
e.clientY + document.body.scrollTop + document.documentElement.scrollTop; | |
} | |
return { | |
x: posX, | |
y: posY | |
}; | |
} | |
docScrolls() { | |
return { | |
x: document.body.scrollLeft + document.documentElement.scrollLeft, | |
y: document.body.scrollTop + document.documentElement.scrollTop | |
} | |
} | |
} | |
class Tilt { | |
constructor(el, options) { | |
this.el = el; | |
this.init(); | |
this.settings = { | |
rotate: { | |
x: 20, | |
y: 20, | |
z: 5 | |
}, | |
translate: { | |
x: 20, | |
y: 20, | |
z: 0 | |
}, | |
lock: false, | |
speed: 1000 | |
} | |
Object.assign(this.settings, options); | |
this.utility = new Utility() | |
this.bindEvents(); | |
} | |
calculateRotation(pos, ref) { | |
const percentageX = pos.x / this.bounds.width; | |
const percentageY = pos.y / this.bounds.height; | |
const percentageZ = pos.x / this.bounds.width; | |
const rX = ref.x - percentageX * ref.x * 2; | |
const rY = ref.y - percentageY * ref.y * 2; | |
const rZ = ref.z - percentageZ * ref.z * 2; | |
return { | |
y: rX, | |
x: rY * -1, | |
z: rZ * -1 | |
}; | |
} | |
calculateTranslation(pos, ref) { | |
const percentageX = pos.x / this.bounds.width; | |
const percentageY = pos.y / this.bounds.height; | |
const percentageZ = pos.x / this.bounds.width; | |
const tX = ref.x - percentageX * ref.x * 2; | |
const tY = ref.y - percentageY * ref.y * 2; | |
const tZ = ref.z - percentageZ * ref.z * 2; | |
return { | |
x: tX * -1, | |
y: tY * -1, | |
z: tZ * -1 | |
}; | |
} | |
init() { | |
this.bounds = this.el.getBoundingClientRect(); | |
} | |
bindEvents() { | |
let mousePos; | |
let bounds = this.el.getBoundingClientRect(); | |
let docScrolls = this.utility.docScrolls(); | |
this.el.addEventListener("mousemove", (e) => { | |
mousePos = this.utility.getMousePosition(e); | |
const relMousePos = { | |
x: mousePos.x - bounds.left - docScrolls.x, | |
y: mousePos.y - bounds.top - docScrolls.y | |
}; | |
const translation = this.calculateTranslation( | |
relMousePos, | |
this.settings.translate | |
); | |
const rotation = this.calculateRotation( | |
relMousePos, | |
this.settings.rotate | |
); | |
this.animate({ | |
translation, | |
rotation | |
}); | |
}); | |
window.addEventListener("resize", this.utility.debounce((e) => { | |
this.bounds = this.el.getBoundingClientRect(); | |
docScrolls = this.utility.docScrolls(); | |
}, 250)) | |
window.addEventListener("scroll", this.utility.debounce((e) => { | |
this.bounds = this.el.getBoundingClientRect(); | |
docScrolls = this.utility.docScrolls(); | |
}, 250)) | |
} | |
animate(ref) { | |
TweenMax.to(this.el.querySelector('img'), this.settings.speed / 1000, { | |
x: ref.translation.x, | |
y: ref.translation.y, | |
z: ref.translation.z, | |
rotationX: ref.rotation.x, | |
rotationY: ref.rotation.y, | |
rotationZ: ref.rotation.z, | |
ease: Power1.easeOut | |
}); | |
} | |
} | |
class Slide { | |
constructor(el) { | |
this.DOM = {}; | |
this.DOM.el = el; | |
this.DOM.wrap = el.querySelector('.slide-wrapper'); | |
this.DOM.imgWrapper = el.querySelector('.img-wrapper'); | |
this.DOM.titleWrap = el.querySelector('.title-wrapper'); | |
this.DOM.title = el.querySelector('.inner-wrapper'); | |
this.config = { | |
animation: { | |
duration: 1, | |
ease: Expo.easeInOut | |
}, | |
tiltOptions: { | |
translate: { | |
x: -10, | |
y: -10, | |
z: 5 | |
}, | |
rotate: { | |
x: 0, | |
y: 0, | |
z: 0 | |
} | |
} | |
}; | |
this.DOM.img = new Tilt(el, this.config.tiltOptions) | |
} | |
setCurrent(isCurrent = true) { | |
this.DOM.el.classList[isCurrent ? 'add' : 'remove']('current'); | |
} | |
hide(direction) { | |
return this.toggle('hide', direction); | |
} | |
show(direction) { | |
this.DOM.el.style.zIndex = 11; | |
return this.toggle('show', direction); | |
} | |
toggle(action, direction) { | |
return new Promise((resolve) => { | |
if (action === 'show') { | |
TweenMax.to(this.DOM.wrap, this.config.animation.duration, { | |
ease: this.config.animation.ease, | |
startAt: { | |
x: direction === 'right' ? '100%' : '-100%' | |
}, | |
x: '0%' | |
}); | |
TweenMax.to(this.DOM.titleWrap, this.config.animation.duration, { | |
ease: this.config.animation.ease, | |
startAt: { | |
x: direction === 'right' ? '-100%' : '100%', | |
}, | |
x: '0%', | |
}); | |
TweenMax.to(this.DOM.title, this.config.animation.duration, { | |
ease: this.config.animation.ease, | |
startAt: { | |
filter: `blur(30px)`, | |
opacity: 0.2 | |
}, | |
filter: `blur(0px)`, | |
opacity: 1 | |
}); | |
} | |
if (action === 'hide') { | |
TweenMax.to(this.DOM.title, this.config.animation.duration, { | |
ease: this.config.animation.ease, | |
startAt: { | |
filter: `blur(0px)`, | |
opacity: 1 | |
}, | |
filter: `blur(30px)`, | |
opacity: 0.2 | |
}); | |
} | |
TweenMax.to(this.DOM.imgWrapper, this.config.animation.duration, { | |
ease: this.config.animation.ease, | |
startAt: action === 'hide' ? {} : { | |
x: direction === 'right' ? '-100%' : '100%', | |
scale: 1.1 | |
}, | |
x: '0%', | |
scale: action === 'hide' ? 1.1 : 1, | |
onStart: () => { | |
this.DOM.imgWrapper.style.transformOrigin = action === 'hide' ? | |
direction === 'right' ? '100% 50%' : '0% 50%' : | |
direction === 'right' ? '0% 50%' : '100% 50%'; | |
this.DOM.el.style.opacity = 1; | |
}, | |
onComplete: () => { | |
this.DOM.el.style.zIndex = 9; | |
this.DOM.el.style.opacity = action === 'hide' ? 0 : 1; | |
resolve(); | |
} | |
}); | |
}); | |
} | |
} | |
class Navigation { | |
constructor(el, settings) { | |
this.DOM = {}; | |
this.DOM.el = el; | |
this.bullets = []; | |
this.settings = { | |
active: 0, | |
onClick: () => false | |
} | |
Object.assign(this.settings, settings); | |
this.init() | |
} | |
init() { | |
Array.from(this.DOM.el.querySelectorAll('.bullet')) | |
.forEach(bullet => { | |
this.bullets.push(bullet) | |
}); | |
this.bullets[this.settings.active].classList.add('current') | |
this.bindEvents() | |
} | |
bindEvents() { | |
this.bullets.forEach((bullet, idx) => { | |
bullet.addEventListener('click', () => { | |
this.settings.onClick(idx) | |
}) | |
}) | |
} | |
setCurrent(idx) { | |
this.bullets.forEach(bullet => { | |
bullet.classList.remove('current') | |
}) | |
this.bullets[idx].classList.add('current') | |
} | |
} | |
class Slider { | |
constructor(el, settings) { | |
this.DOM = {}; | |
this.DOM.el = el; | |
this.slides = []; | |
this.settings = { | |
currentSlide: 0, | |
} | |
Object.assign(this.settings, settings); | |
this.init(); | |
} | |
init() { | |
this.navigation = new Navigation(document.querySelector('#navigation'), { | |
active: this.settings.currentSlide, | |
onClick: (idx) => this.navigate(idx) | |
}); | |
Array.from(this.DOM.el.querySelectorAll('.slide')) | |
.forEach((slide) => { | |
this.slides.push(new Slide(slide)) | |
}); | |
this.slides[this.settings.currentSlide].setCurrent(); | |
} | |
async navigate(idx) { | |
if (this.isAnimating || idx === this.settings.currentSlide) return; | |
this.isAnimating = true; | |
const direction = idx > this.settings.currentSlide ? 'right' : 'left' | |
this.navigation.setCurrent(idx) | |
await Promise.all([this.slides[this.settings.currentSlide].hide(direction), this.slides[idx].show(direction)]) | |
this.slides[this.settings.currentSlide].setCurrent(false); | |
this.settings.currentSlide = idx; | |
this.slides[this.settings.currentSlide].setCurrent(); | |
this.isAnimating = false; | |
} | |
} | |
const sliderEl = document.querySelector('#slider'); | |
const slider = new Slider(sliderEl); |
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
.wrapper { | |
height: 100%; | |
width: 100%; | |
} | |
#slider { | |
width: 100%; | |
height: 100%; | |
overflow: hidden; | |
position: relative; | |
} | |
.slide { | |
position: absolute; | |
overflow: hidden; | |
top: 0; | |
left: 0; | |
bottom: 0; | |
right: 0; | |
pointer-events: none; | |
} | |
.slide.current { | |
pointer-events: initial; | |
opacity: 1; | |
z-index: 10; | |
} | |
.slide-wrapper { | |
width: 100%; | |
height: 100%; | |
overflow: hidden; | |
position: relative; | |
} | |
.img-wrapper { | |
width: 100%; | |
height: 100%; | |
background: rgba(46, 61, 102, 0.6) | |
} | |
.img-wrapper img { | |
width: calc(100% + 20px); | |
height: calc(100% + 20px); | |
top: calc(20px / 2 * -1); | |
left: calc(20px / 2 * -1); | |
position: relative; | |
object-fit: cover; | |
mix-blend-mode: luminosity | |
} | |
.title-wrapper { | |
position: absolute; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
background: linear-gradient(to top, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 100%) | |
} | |
.inner-wrapper { | |
position: absolute; | |
bottom: 5em; | |
left: 5em; | |
color: rgb(255, 255, 255); | |
max-width: 70%; | |
} | |
.slide-title { | |
white-space: nowrap; | |
text-transform: uppercase; | |
font-size: 4em; | |
font-weight: 900; | |
} | |
.slide-subtitle { | |
font-size: 1.5em; | |
} | |
#navigation { | |
pointer-events: none; | |
position: absolute; | |
bottom: 0; | |
z-index: 15; | |
width: 100%; | |
height: 50px; | |
display: grid; | |
grid-template-columns: repeat(5, 60px); | |
grid-gap: 16px; | |
justify-content: center; | |
align-items: center; | |
} | |
.bullet { | |
pointer-events: initial; | |
border-radius: 10px; | |
cursor: pointer; | |
height: 5px; | |
background: rgb(255, 255, 255); | |
transition: opacity 0.3s ease; | |
opacity: 0.2; | |
} | |
.bullet.current { | |
opacity: 1; | |
} | |
.bullet:not(.current):hover { | |
opacity: 0.5; | |
} | |
@media (max-width: 700px) { | |
.slide-title { | |
font-size: 3em | |
} | |
.slide-subtitle { | |
font-size: 1.2em; | |
} | |
} | |
@media (max-width: 500px) { | |
.inner-wrapper { | |
left: initial; | |
width: 100%; | |
max-width: initial; | |
} | |
.slide-title { | |
font-size: 4em; | |
text-align: center | |
} | |
.slide-subtitle { | |
display: none | |
} | |
} | |
@media (max-width: 450px) { | |
.slide-title { | |
font-size: 3em; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment