Created
July 19, 2025 14:31
-
-
Save tolepcoy/87da12b158d5257818c8c7fe32f4a54b to your computer and use it in GitHub Desktop.
Slider Animations
This file contains hidden or 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
| <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> |
This file contains hidden or 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
| 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); |
This file contains hidden or 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
| * { | |
| 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