Skip to content

Instantly share code, notes, and snippets.

@tolepcoy
Created July 19, 2025 14:31
Show Gist options
  • Save tolepcoy/87da12b158d5257818c8c7fe32f4a54b to your computer and use it in GitHub Desktop.
Save tolepcoy/87da12b158d5257818c8c7fe32f4a54b to your computer and use it in GitHub Desktop.
Slider Animations
<div class="slider-container">
<div class="content">
<div class="color-overlay"></div>
<div class="product-name">
<span class="word-part first-word">Chai</span>
<span class="word-part second-word">Vanilla</span>
</div>
<img class="milkshake-image" src="https://raw.githubusercontent.com/nidal1111/storage/master/assets/milkshake_banana.png" alt="Milkshake">
<div class="nutrition-panel">
<div class="nutrition-item">
<div class="nutrition-value">20g</div>
<div class="nutrition-label">Plant Protein</div>
</div>
<div class="nutrition-item">
<div class="nutrition-value">13g</div>
<div class="nutrition-label">Of Fiber</div>
</div>
<div class="nutrition-item">
<div class="nutrition-value">15</div>
<div class="nutrition-label">Vitamins</div>
</div>
<div class="nutrition-item">
<div class="nutrition-value">1.8g</div>
<div class="nutrition-label">Omega-3</div>
</div>
<div class="nutrition-item">
<div class="nutrition-value">1B</div>
<div class="nutrition-label">CFU Probiotics</div>
</div>
</div>
</div>
<div class="controls">
<div class="control-dot active" data-index="0"></div>
<div class="control-dot" data-index="1"></div>
<div class="control-dot" data-index="2"></div>
<div class="control-dot" data-index="3"></div>
</div>
</div>
const flavors = [
{
name: ["Chai", "Vanilla"],
color: "#4A90E2",
image:
"https://raw.githubusercontent.com/nidal1111/storage/master/assets/milkshake_banana.png",
nutrition: ["20g", "13g", "15", "1.8g", "1B"]
},
{
name: ["Maple", "Peanut"],
color: "#E94B4B",
image:
"https://raw.githubusercontent.com/nidal1111/storage/master/assets/milkShake_caffe%CC%80.png",
nutrition: ["35g", "10g", "10", "1.5g", "2B"]
},
{
name: ["Cacao", "Coconut"],
color: "#F4D03F",
image:
"https://raw.githubusercontent.com/nidal1111/storage/master/assets/milkShake_fragole.png",
nutrition: ["40g", "25g", "22", "2.2g", "1B"]
},
{
name: ["Berry", "Blend"],
color: "#8E44AD",
image:
"https://raw.githubusercontent.com/nidal1111/storage/master/assets/milkshake_banana.png",
nutrition: ["28g", "18g", "25", "2.0g", "3B"]
}
];
let currentIndex = 0;
let isAnimating = false;
const container = document.querySelector(".slider-container");
const overlay = document.querySelector(".color-overlay");
const firstWord = document.querySelector(".first-word");
const secondWord = document.querySelector(".second-word");
const imageElement = document.querySelector(".milkshake-image");
const nutritionValues = document.querySelectorAll(".nutrition-value");
const dots = document.querySelectorAll(".control-dot");
function initSlider() {
const currentFlavor = flavors[currentIndex];
container.style.background = currentFlavor.color;
firstWord.textContent = currentFlavor.name[0];
secondWord.textContent = currentFlavor.name[1];
imageElement.src = currentFlavor.image;
}
function morphWords(fromWords, toWords, onComplete) {
const [fromFirst, fromSecond] = fromWords;
const [toFirst, toSecond] = toWords;
firstWord.style.transform = "translateX(0)";
secondWord.style.transform = "translateX(0)";
const maxMoveDistance = 20;
let step = 0;
const totalSteps = 40;
function nextFrame() {
if (step < totalSteps) {
const progress = step / (totalSteps - 1);
const easeProgress = progress * progress * progress;
const moveDistance = maxMoveDistance * easeProgress;
firstWord.style.transform = `translateX(${moveDistance}px)`;
secondWord.style.transform = `translateX(-${moveDistance}px)`;
const firstCharsToShow = Math.max(
0,
Math.ceil(fromFirst.length * (1 - easeProgress))
);
const secondCharsToShow = Math.max(
0,
Math.ceil(fromSecond.length * (1 - easeProgress))
);
const currentFirst = fromFirst.substring(0, firstCharsToShow);
const currentSecond = fromSecond.substring(
fromSecond.length - secondCharsToShow
);
if (currentFirst !== firstWord.textContent) {
firstWord.textContent = currentFirst;
}
if (currentSecond !== secondWord.textContent) {
secondWord.textContent = currentSecond;
}
step++;
requestAnimationFrame(nextFrame);
} else {
setTimeout(() => {
let expandStep = 0;
const expandSteps = 40;
function expandFrame() {
const expandProgress = expandStep / (expandSteps - 1);
const easeExpandProgress =
expandProgress * expandProgress * (3 - 2 * expandProgress);
const returnDistance = maxMoveDistance * (1 - easeExpandProgress);
firstWord.style.transform = `translateX(${returnDistance}px)`;
secondWord.style.transform = `translateX(-${returnDistance}px)`;
const firstCharsToShow = Math.ceil(
toFirst.length * easeExpandProgress
);
const secondCharsToShow = Math.ceil(
toSecond.length * easeExpandProgress
);
const currentFirst = toFirst.substring(0, firstCharsToShow);
const currentSecond = toSecond.substring(
toSecond.length - secondCharsToShow
);
if (currentFirst !== firstWord.textContent) {
firstWord.textContent = currentFirst;
}
if (currentSecond !== secondWord.textContent) {
secondWord.textContent = currentSecond;
}
if (expandStep < expandSteps) {
expandStep++;
requestAnimationFrame(expandFrame);
} else {
firstWord.style.transform = "translateX(0)";
secondWord.style.transform = "translateX(0)";
firstWord.textContent = toFirst;
secondWord.textContent = toSecond;
if (onComplete) onComplete();
}
}
expandFrame();
}, 100);
}
}
nextFrame();
}
function animateNutritionValues(newValues) {
nutritionValues.forEach((value, index) => {
setTimeout(() => {
value.style.opacity = "0";
setTimeout(() => {
value.textContent = newValues[index];
value.style.opacity = "1";
}, 150);
}, index * 80);
});
}
function changeSlide(newIndex) {
if (newIndex === currentIndex || isAnimating) return;
isAnimating = true;
const currentFlavor = flavors[currentIndex];
const newFlavor = flavors[newIndex];
overlay.style.background = newFlavor.color;
overlay.classList.add("slide-down");
setTimeout(() => {
morphWords(currentFlavor.name, newFlavor.name);
animateNutritionValues(newFlavor.nutrition);
setTimeout(() => {
imageElement.src = newFlavor.image;
imageElement.style.opacity = "0";
setTimeout(() => {
container.style.background = newFlavor.color;
overlay.classList.remove("slide-down");
overlay.style.transform = "translateY(-100%)";
setTimeout(() => {
imageElement.style.opacity = "1";
overlay.style.transform = "";
isAnimating = false;
}, 300);
}, 100);
}, 400);
}, 0);
dots[currentIndex].classList.remove("active");
dots[newIndex].classList.add("active");
currentIndex = newIndex;
}
function autoSlide() {
if (!isAnimating) {
const nextIndex = (currentIndex + 1) % flavors.length;
changeSlide(nextIndex);
}
}
dots.forEach((dot, index) => {
dot.addEventListener("click", () => changeSlide(index));
});
initSlider();
setInterval(autoSlide, 4000);
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #2c3e50;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
}
.slider-container {
position: relative;
width: 600px;
height: 400px;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
background: #4a90e2;
}
.color-overlay {
position: absolute;
top: -100%;
left: 0;
width: 100%;
height: 100%;
background: #e94b4b;
transition: transform 1.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
z-index: 10;
}
.color-overlay.slide-down {
transform: translateY(100%);
}
.content {
position: relative;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
z-index: 5;
}
.product-name {
font-size: 48px;
font-weight: bold;
color: white;
text-align: center;
margin: 20px 0;
min-height: 60px;
display: flex;
align-items: center;
justify-content: center;
z-index: 15;
position: relative;
margin-top: -2px;
gap: 70px;
}
.word-part {
transition: transform 0.8s ease-out;
display: inline-block;
}
.milkshake-image {
width: 400px;
height: 300px;
margin: 5px 0;
object-fit: contain;
transition: opacity 1.5s ease;
position: relative;
z-index: 5;
position: absolute;
}
.nutrition-panel {
background: white;
border-radius: 15px;
padding: 20px 30px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 19px;
margin-top: 15px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
position: relative;
z-index: 15;
min-width: 95%;
}
.nutrition-item {
text-align: center;
flex: 1;
}
.nutrition-value {
font-size: 16px;
font-weight: bold;
color: #2c3e50;
margin-bottom: 5px;
transition: all 0.4s ease;
}
.nutrition-label {
font-size: 12px;
color: #7f8c8d;
line-height: 1.2;
}
.controls {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 10px;
z-index: 15;
}
.control-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.4);
cursor: pointer;
transition: all 0.3s ease;
}
.control-dot.active {
background: white;
transform: scale(1.2);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment