Skip to content

Instantly share code, notes, and snippets.

@alamindevms
Created August 12, 2022 05:39
Show Gist options
  • Save alamindevms/a899b76dba1f5f5beb0e51bfcc12351a to your computer and use it in GitHub Desktop.
Save alamindevms/a899b76dba1f5f5beb0e51bfcc12351a to your computer and use it in GitHub Desktop.
Carousel with gsap animation, tailwindcss
<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>
// 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);
.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