Add to cart animation

AetherFlow: Ethereal E-commerce UI with Fluid Magnetic Cart Animations

May 27, 2025
49 views
CSS GSAP HTML JavaScript


AetherFlow: Ethereal E-commerce UI with Fluid Magnetic Cart Animations


AetherFlow - Celestial Cart" presents a highly polished and unique e-commerce interface, emphasizing an ethereal, light, and organic aesthetic. The core interaction involves a "magnetic cart pull," where adding an item creates a soft, glowing orb that fluidly drifts towards the main cart icon when the user's mouse hovers nearby, accelerating gently as it approaches. The design features a soft, desaturated color palette with iridescent accent gradients, rounded organic shapes, delicate typography (Quicksand & Nunito Sans), and exceptionally smooth animations powered by GSAP. AOS enhances the initial page load with graceful transitions.

AetherFlow: Crafting a Serene Shopping Experience with Fluid Dynamics and Ethereal Design

The "AetherFlow - Celestial Cart" project is a testament to sophisticated front-end design, aiming to create a serene, almost magical e-commerce experience. It moves away from conventional sharp lines and bold colors, instead embracing an ethereal aesthetic characterized by soft, desaturated whites and creams, and an iridescent accent gradient of pale blues, lavenders, and corals. This gentle palette, combined with the rounded, organic shapes of UI elements like product cards and buttons, contributes to a calm and inviting atmosphere. Typography choices—Quicksand for headings and Nunito Sans for body text—further enhance this soft, modern feel.
The centerpiece of AetherFlow is its unique "Magnetic Cart Pull" interaction. When a user adds an item to their bag, a small, softly glowing orb, styled with a radial gradient and subtle pulse animation, gracefully materializes near the "Add to Bag" button. This orb represents the added item. The magic happens when the user's mouse cursor approaches the main navigation cart icon; the orb begins to drift towards it, its movement simulated using a custom JavaScript physics engine managed by requestAnimationFrame and smoothly interpolated by GSAP. The pull is not abrupt but rather like an object caught in a gentle current, accelerating subtly as it nears its destination. The cart icon itself emits soft, animated ripples when hovered over, visually indicating its "magnetic field." Upon reaching the cart icon, the orb elegantly merges into it with a final soft glow and ripple effect.
Beyond this core feature, the entire user interface is polished with meticulous attention to detail. Product cards feature taller image containers and an airy layout, with AOS (Animate On Scroll) providing varied and smooth entry animations (e.g., fade-up-right, fade-up-left) for a dynamic yet graceful page load. The slide-in cart panel, activated by clicking the cart icon, maintains the light and airy theme. It has been carefully engineered to handle item additions, quantity changes, and removals with fluid GSAP animations, ensuring the list updates seamlessly without visual jarring. The cart panel's internal elements, including the empty state message and item rendering, are robustly managed for a flawless user experience. All animations and transitions are orchestrated by GSAP, with custom easings and timings chosen to reflect the "AetherFlow" concept of effortless, flowing movement.
HTML (html)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Festive Cart - Confetti Pop Deluxe</title> <link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&display=swap" rel="stylesheet" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.2/dist/confetti.browser.min.js"></script> <link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet" /> <script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script> <link rel="stylesheet" href="style.css" /> </head> <body> <header data-aos="fade-down" data-aos-duration="900" data-aos-easing="ease-out-cubic" > <div class="container"> <nav> <div class="logo"> <i class="fas fa-gifts"></i> <span>Festive Cart</span> </div> <div class="nav-cart-icon-wrapper" id="navCartIconWrapper"> <i class="fas fa-shopping-bag nav-cart-icon"></i> <span class="nav-cart-count" id="navCartCount">0</span> </div> </nav> </div> </header> <main class="container"> <div class="products-grid" id="productsGrid"> <!-- Products --> </div> </main> <div class="cart-overlay" id="cartOverlay"></div> <div class="cart-panel" id="cartPanel"> <div class="cart-panel__header"> <div class="cart-panel__title"> <i class="fas fa-box-open"></i><span>Your Festive Finds</span> </div> <button class="cart-panel__close-btn" id="closeCartBtn"> <i class="fas fa-times"></i> </button> </div> <div class="cart-panel__content-area"> <div id="cartItemsContainer"></div> <div id="emptyCartMessage" class="cart-panel__empty-cart"> <i class="far fa-surprise"></i> <p>Your cart is ready for a party!</p> <p>Add some festive items.</p> </div> </div> <div class="cart-panel__footer"> <div class="cart-panel__total"> <span>Subtotal:</span ><span class="cart-panel__total-amount" id="cartTotalAmount" >$0.00</span > </div> <button class="checkout-btn" id="checkoutBtn" disabled> <i class="fas fa-credit-card"></i><span>Proceed to Checkout</span> </button> </div> </div> <script src="script.js"></script> </body> </html>
CSS (css)
:root { --bg-main: #fdfeff; /* Brighter white */ --bg-card: #ffffff; --bg-panel: #ffffff; --text-primary: #212529; /* Darker, richer black */ --text-secondary: #495057; --text-light: #ced4da; --accent-vibrant-pink-start: #ff6b6b; --accent-vibrant-pink-end: #ff9a8b; --accent-vibrant-orange-start: #ffd07b; --accent-vibrant-orange-end: #ffab76; /* Using Pink/Orange as primary for more festivity */ --accent-primary-start: var(--accent-vibrant-pink-start); --accent-primary-end: var(--accent-vibrant-orange-end); --accent-gradient: linear-gradient( 135deg, var(--accent-primary-start), var(--accent-primary-end) ); --accent-solid: var(--accent-vibrant-pink-start); --confetti-colors: [ "#FF6B6B", "#FF9A8B", "#FFD07B", "#FFAB76", "#FFFFFF", "#4ECDC4", "#52BE80"]; --success-color: #20c997; /* Tealish Green */ --border-color: #e9ecef; /* Lighter border */ --shadow-subtle: 0 3px 10px rgba(0, 0, 0, 0.04); --shadow-card-hover: 0 8px 25px rgba(0, 0, 0, 0.08); --shadow-button-active: 0 6px 18px rgba(255, 107, 107, 0.3); --radius-modern: 10px; --radius-card: 16px; --radius-button-pill: 50px; --font-main: "DM Sans", sans-serif; --transition-interactive: 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: var(--font-main); background-color: var(--bg-main); color: var(--text-primary); line-height: 1.65; min-height: 100vh; overflow-x: hidden; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .container { max-width: 1250px; margin: 0 auto; padding: 0 1.5rem; } header { background-color: rgba(253, 254, 255, 0.92); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); position: sticky; top: 0; z-index: 1000; padding: 1.1rem 0; } nav { display: flex; justify-content: space-between; align-items: center; } .logo { font-size: 2rem; font-weight: 700; display: flex; align-items: center; gap: 0.6rem; color: var(--accent-solid); letter-spacing: -0.5px; } .logo i { color: var(--accent-primary-end); filter: drop-shadow(0 0 5px var(--accent-primary-end)); } .nav-cart-icon-wrapper { position: relative; cursor: pointer; padding: 10px; border-radius: 50%; transition: background-color 0.3s ease, transform 0.3s ease; } .nav-cart-icon-wrapper:hover { background-color: rgba(255, 107, 107, 0.07); transform: scale(1.05); } .nav-cart-icon { font-size: 1.75rem; color: var(--text-secondary); transition: color 0.3s ease, transform 0.3s ease; } .nav-cart-icon-wrapper:hover .nav-cart-icon { color: var(--accent-solid); transform: scale(1.1); } .nav-cart-count { position: absolute; top: 2px; right: 2px; background: var(--accent-gradient); color: var(--bg-card); font-size: 0.75rem; font-weight: 700; width: 22px; height: 22px; border-radius: 50%; display: flex; justify-content: center; align-items: center; box-shadow: 0 3px 8px rgba(255, 107, 107, 0.5); border: 2px solid var(--bg-card); } /* Products Grid */ .products-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 2.5rem; padding: 3.5rem 0; } .product-card { background-color: var(--bg-card); border-radius: var(--radius-card); box-shadow: var(--shadow-subtle); transition: transform 0.35s ease, box-shadow 0.35s ease; display: flex; flex-direction: column; position: relative; border: 1px solid var(--border-color); } .product-card:hover { transform: translateY(-8px); box-shadow: var(--shadow-card-hover); } .product-card__image-container { position: relative; height: 230px; overflow: hidden; background-color: #f1f3f5; border-radius: var(--radius-card) var(--radius-card) 0 0; } .product-card__image-container img { width: 100%; height: 100%; object-fit: cover; transition: transform 0.55s cubic-bezier(0.25, 0.46, 0.45, 0.94); } .product-card:hover .product-card__image-container img { transform: scale(1.07); } .product-card__info { padding: 1.75rem; display: flex; flex-direction: column; flex-grow: 1; } .product-card__title { font-size: 1.2rem; font-weight: 700; margin-bottom: 0.5rem; color: var(--text-primary); } .product-card__price { font-size: 1.3rem; font-weight: 500; color: var(--accent-solid); margin-bottom: 1.1rem; } .product-card__description { font-size: 0.9rem; color: var(--text-secondary); flex-grow: 1; margin-bottom: 1.5rem; line-height: 1.6; } .add-to-cart-btn { background: var(--accent-gradient); color: var(--bg-card); border: none; padding: 0.85rem 2rem; border-radius: var(--radius-button-pill); font-weight: 700; font-size: 0.95rem; cursor: pointer; transition: transform 0.25s ease, box-shadow 0.3s ease, filter 0.25s ease; display: flex; align-items: center; justify-content: center; gap: 0.6rem; margin-top: auto; box-shadow: var(--shadow-button-active); letter-spacing: 0.3px; } .add-to-cart-btn:hover:not([disabled]) { transform: translateY(-3px) scale(1.02); box-shadow: 0 8px 20px rgba(255, 107, 107, 0.4); filter: brightness(1.08); } .add-to-cart-btn:active:not([disabled]) { transform: translateY(-1px) scale(0.97); } .add-to-cart-btn.processing { pointer-events: none; filter: saturate(0.8) brightness(0.9); } .add-to-cart-btn.added { background: linear-gradient( 135deg, var(--success-color), #20c997 60%, #1db954 ); color: white; pointer-events: none; box-shadow: 0 4px 15px rgba(32, 201, 151, 0.4); } .add-to-cart-btn.added .btn-text, .add-to-cart-btn.added .btn-icon { display: none; } .add-to-cart-btn.added::after { content: "Added! 🎊"; font-weight: 700; } /* Cart Panel Styling */ .cart-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(33, 37, 41, 0.55); z-index: 1900; opacity: 0; visibility: hidden; } .cart-panel { position: fixed; top: 0; right: -100%; width: 100%; max-width: 400px; height: 100%; background-color: var(--bg-panel); z-index: 2000; box-shadow: -8px 0 25px rgba(0, 0, 0, 0.1); display: flex; flex-direction: column; } .cart-panel__header { display: flex; justify-content: space-between; align-items: center; padding: 1.5rem; border-bottom: 1px solid var(--border-color); } .cart-panel__title { font-size: 1.35rem; font-weight: 700; display: flex; align-items: center; gap: 0.7rem; color: var(--text-primary); } .cart-panel__title i { color: var(--accent-solid); } .cart-panel__close-btn { background: none; border: none; font-size: 1.75rem; cursor: pointer; color: var(--text-secondary); transition: transform 0.3s ease, color 0.3s ease; } .cart-panel__close-btn:hover { transform: rotate(180deg) scale(1.1); color: var(--accent-solid); } .cart-panel__content-area { flex-grow: 1; overflow-y: auto; padding: 1.5rem; position: relative; } .cart-panel__content-area::-webkit-scrollbar { width: 7px; } .cart-panel__content-area::-webkit-scrollbar-track { background: #f1f3f5; border-radius: 10px; } .cart-panel__content-area::-webkit-scrollbar-thumb { background: var(--accent-primary-start); border-radius: 10px; } .cart-panel__content-area::-webkit-scrollbar-thumb:hover { background: var(--accent-primary-end); } #cartItemsContainer { position: relative; } .cart-panel__empty-cart { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: var(--text-secondary); text-align: center; opacity: 0; position: absolute; top: 0; left: 0; width: 100%; pointer-events: none; padding: 2.5rem; } .cart-panel__empty-cart.visible { opacity: 1; pointer-events: auto; } .cart-panel__empty-cart i { font-size: 5.5rem; margin-bottom: 1.25rem; color: var(--text-light); } .cart-panel__empty-cart p { font-size: 1.1rem; } .cart-item { display: flex; gap: 1.25rem; padding: 1.25rem; border-radius: var(--radius-modern); background-color: #f8f9fa; margin-bottom: 1.25rem; box-shadow: var(--shadow-subtle); position: relative; border: 1px solid var(--border-color); transition: transform 0.2s ease; } .cart-item:hover { transform: translateX(-4px); } .cart-item__img-container { width: 80px; height: 80px; border-radius: var(--radius-modern); overflow: hidden; flex-shrink: 0; background-color: #e9ecef; } .cart-item__img-container img { width: 100%; height: 100%; object-fit: cover; } .cart-item__info { flex-grow: 1; display: flex; flex-direction: column; justify-content: space-between; } .cart-item__title { font-weight: 500; font-size: 1rem; margin-bottom: 0.3rem; } .cart-item__price { color: var(--accent-solid); font-weight: 700; font-size: 0.95rem; margin-bottom: 0.6rem; } .cart-item__quantity-controls { display: flex; align-items: center; gap: 0.7rem; } .quantity-btn { width: 30px; height: 30px; border-radius: 50%; border: 1px solid var(--border-color); background-color: var(--bg-card); display: flex; justify-content: center; align-items: center; cursor: pointer; color: var(--text-secondary); transition: all 0.25s ease; font-size: 0.85rem; } .quantity-btn:hover { background-color: var(--accent-solid); color: var(--bg-card); border-color: var(--accent-solid); transform: scale(1.05); } .cart-item__quantity { font-weight: 500; font-size: 1rem; min-width: 20px; text-align: center; } .cart-item__remove-btn { position: absolute; top: 0.8rem; right: 0.8rem; color: var(--text-light); background: none; border: none; cursor: pointer; font-size: 1.2rem; padding: 0.25rem; transition: color 0.25s ease, transform 0.25s ease; } .cart-item__remove-btn:hover { color: #e74c3c; transform: scale(1.15); } .cart-panel__footer { padding: 1.75rem; border-top: 1px solid var(--border-color); background-color: #f8f9fa; } .cart-panel__total { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; color: var(--text-secondary); } .cart-panel__total span:first-child { font-size: 1.1rem; font-weight: 500; } .cart-panel__total-amount { font-size: 1.4rem; font-weight: 700; color: var(--accent-solid); } .checkout-btn { width: 100%; padding: 0.9rem; border-radius: var(--radius-button-pill); background: var(--accent-gradient); color: var(--bg-card); border: none; font-weight: 700; font-size: 1.05rem; cursor: pointer; transition: transform 0.25s ease, box-shadow 0.35s ease, filter 0.25s ease; display: flex; justify-content: center; align-items: center; gap: 0.7rem; box-shadow: var(--shadow-button-active); } .checkout-btn:hover:not(:disabled) { transform: translateY(-3px); box-shadow: 0 10px 22px rgba(255, 107, 107, 0.4); filter: brightness(1.1); } .checkout-btn:disabled { background: var(--text-light); color: var(--text-secondary); cursor: not-allowed; box-shadow: none; transform: none; filter: none; } /* Responsive */ @media (max-width: 992px) { .products-grid { grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); } } @media (max-width: 768px) { .container { padding: 0 1.25rem; } .cart-panel { max-width: 90vw; } } @media (max-width: 480px) { .logo { font-size: 1.7rem; } .nav-cart-icon { font-size: 1.65rem; } .products-grid { grid-template-columns: 1fr; gap: 2rem; } .product-card__image-container { height: 220px; } .product-card__info { padding: 1.5rem; } .cart-panel__title { font-size: 1.25rem; } .cart-panel__content-area, .cart-panel__footer { padding: 1.25rem; } }
JAVASCRIPT (javascript)
// All JavaScript code will be placed here document.addEventListener("DOMContentLoaded", () => { AOS.init({ duration: 850, delay: 120, once: true, offset: 100, easing: "cubic-bezier(0.25, 0.46, 0.45, 0.94)", }); // --- SOUND EFFECT PLACEHOLDER --- // const popSound = new Audio('YOUR_SOUND_FILE.mp3'); // Replace with your sound file // try { // popSound.volume = 0.4; // Adjust volume // } catch(e) { console.warn("Could not set sound volume, audio may not play.", e); } // --- END SOUND EFFECT PLACEHOLDER --- const productData = [ { id: "fc01", name: "Gourmet Chocolate Box", price: 29.99, image: "https://images.unsplash.com/photo-1587668178277-295251f900ce?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxzZWFyY2h8Mnx8Y2hvY29sYXRlJTIwYm94fGVufDB8fDB8fHww&auto=format&fit=crop&w=300&h=220&q=80", description: "A delightful assortment of handcrafted chocolates.", }, { id: "fc02", name: "Sparkling Celebration Drink", price: 15.5, image: "https://plus.unsplash.com/premium_photo-1665949502498-b5250d637851?fm=jpg&q=60&w=3000&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxzZWFyY2h8NXx8c3BhcmtsaW5nJTIwd2luZXxlbnwwfHwwfHx8MA%3D%3D", description: "Non-alcoholic bubbly for any occasion.", }, { id: "fc03", name: "Festive Cookie Tin", price: 22.0, image: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRRyAQF6IruFy7TwiftFSNyntb1sImFldNXeA&s", description: "Assorted holiday cookies in a keepsake tin.", }, { id: "fc04", name: "Cozy Knit Beanie", price: 18.75, image: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRRyAQF6IruFy7TwiftFSNyntb1sImFldNXeA&s", description: "Warm and stylish for chilly days.", }, { id: "fc05", name: "Aromatic Scented Candle", price: 25.0, image: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTff7bXvMrrc8Ej7k8grELgkjdzYmkjgITD2g&s", description: "Fills your home with a cozy fragrance.", }, { id: "fc06", name: "Luxury Bath Bomb Set", price: 32.99, image: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTBZ5vldpDEUzlM3WvfK0GbXgmmd3QVc1xzAA&s", description: "Indulgent bath bombs for a relaxing soak.", }, ]; // DOM Cache const productsGrid = document.getElementById("productsGrid"); const navCartIconWrapper = document.getElementById("navCartIconWrapper"); const navCartCount = document.getElementById("navCartCount"); const cartOverlay = document.getElementById("cartOverlay"); const cartPanel = document.getElementById("cartPanel"); const closeCartBtn = document.getElementById("closeCartBtn"); const cartItemsContainer = document.getElementById("cartItemsContainer"); const emptyCartMessage = document.getElementById("emptyCartMessage"); const cartTotalAmount = document.getElementById("cartTotalAmount"); const checkoutBtn = document.getElementById("checkoutBtn"); let cart = []; // Confetti default settings const confettiColors = getComputedStyle(document.documentElement) .getPropertyValue("--confetti-colors") .replace(/[\[\]']+/g, "") .split(", ") .map((color) => color.trim()); const confettiDefaults = { spread: 90, // Wider spread origin: { y: 0.6 }, particleCount: 80, // More particles scalar: 1, // Slightly larger particles ticks: 200, // Longer duration disableForReducedMotion: true, colors: confettiColors, shapes: ["square", "circle", "star"], // Add star shape }; function fireConfetti(originX, originY) { confetti({ ...confettiDefaults, origin: { x: originX, y: originY }, angle: gsap.utils.random(45, 135), startVelocity: gsap.utils.random(30, 50), gravity: 0.7, // Make them fall a bit faster drift: gsap.utils.random(-0.5, 0.5), // Add some horizontal drift }); } function renderProducts() { productsGrid.innerHTML = ""; productData.forEach((product, index) => { const productCard = document.createElement("div"); productCard.className = "product-card"; productCard.setAttribute("data-aos", "fade-up"); productCard.setAttribute("data-aos-delay", (index % 3) * 100 + 50); productCard.innerHTML = ` <div class="product-card__image-container"> <img src="${product.image}" alt="${ product.name }" id="product-img-${ product.id }" onerror="this.style.display='none'; this.parentElement.style.background='#E9ECEF url(https://via.placeholder.com/300x220/E9ECEF/6C757D?text=No+Image) center/contain no-repeat';"> </div> <div class="product-card__info"> <h3 class="product-card__title">${product.name}</h3> <p class="product-card__price">$${product.price.toFixed( 2 )}</p> <p class="product-card__description">${ product.description }</p> <button class="add-to-cart-btn" data-id="${product.id}"> <span class="btn-text">Add to Cart</span> <i class="fas fa-cart-plus btn-icon"></i> </button> </div> `; productsGrid.appendChild(productCard); }); } function handleAddToCart(event) { const button = event.target.closest(".add-to-cart-btn"); if ( !button || button.classList.contains("added") || button.classList.contains("processing") ) return; button.classList.add("processing"); gsap .timeline({ onComplete: () => { button.classList.remove("processing"); button.classList.add("added"); setTimeout(() => button.classList.remove("added"), 1600); }, }) .to(button, { scale: 1.15, duration: 0.12, ease: "power3.out" }) .to(button, { scale: 0.9, duration: 0.1, ease: "power3.in" }) .to(button, { scale: 1, duration: 0.25, ease: "back.out(2)" }); const buttonRect = button.getBoundingClientRect(); // Calculate origin relative to viewport for confetti const originX = (buttonRect.left + buttonRect.width / 2) / window.innerWidth; const originY = (buttonRect.top + buttonRect.height / 2) / window.innerHeight; // Multiple confetti bursts for a bigger effect for (let i = 0; i < 3; i++) { setTimeout(() => fireConfetti(originX, originY), i * 60); } // --- PLAY SOUND --- // try { if (popSound.readyState >= 2) popSound.currentTime = 0; popSound.play(); } // catch(e) { console.warn("Sound effect failed.", e); } // --- END PLAY SOUND --- const productId = button.dataset.id; const product = productData.find((p) => p.id === productId); if (!product) return; const existingItem = cart.find((item) => item.id === productId); if (existingItem) { existingItem.quantity++; } else { cart.push({ ...product, quantity: 1 }); } updateCart(); } function updateCart() { renderCartItems(); updateCartCount(); updateCartTotal(); updateCheckoutButtonState(); } function renderCartItems() { if (cart.length === 0) { if (!emptyCartMessage.classList.contains("visible")) { gsap.to(emptyCartMessage, { opacity: 1, duration: 0.4, ease: "power2.out", onStart: () => emptyCartMessage.classList.add("visible"), }); cartItemsContainer.innerHTML = ""; // Clear if cart becomes empty } } else { if (emptyCartMessage.classList.contains("visible")) { gsap.to(emptyCartMessage, { opacity: 0, duration: 0.3, ease: "power2.in", onComplete: () => emptyCartMessage.classList.remove("visible"), }); } } const currentDOMItemElements = Array.from( cartItemsContainer.querySelectorAll(".cart-item") ); const cartItemIds = cart.map((item) => item.id); currentDOMItemElements.forEach((domItem) => { if (!cart.find((cartItem) => cartItem.id === domItem.dataset.itemId)) { gsap.to(domItem, { opacity: 0, height: 0, scaleY: 0, paddingTop: 0, paddingBottom: 0, marginTop: 0, marginBottom: 0, duration: 0.35, ease: "power1.in", onComplete: () => domItem.remove(), }); } }); cart.forEach((item, index) => { let cartItemDiv = cartItemsContainer.querySelector( `.cart-item[data-item-id="${item.id}"]` ); const itemTotal = (item.price * item.quantity).toFixed(2); if (!cartItemDiv) { cartItemDiv = document.createElement("div"); cartItemDiv.className = "cart-item"; cartItemDiv.dataset.itemId = item.id; gsap.set(cartItemDiv, { opacity: 0, y: 20, height: "auto", scaleY: 0, transformOrigin: "top", }); cartItemDiv.innerHTML = ` <div class="cart-item__img-container"><img src="${item.image}" alt="${item.name}" onerror="this.style.display='none'; this.parentElement.style.background='#E9ECEF url(https://via.placeholder.com/80x80/E9ECEF/6C757D?text=N/A) center/contain no-repeat';"></div> <div class="cart-item__info"> <div><h4 class="cart-item__title">${item.name}</h4><p class="cart-item__price">$${itemTotal}</p></div> <div class="cart-item__quantity-controls"> <button class="quantity-btn decrease-qty" data-id="${item.id}"><i class="fas fa-minus"></i></button> <span class="cart-item__quantity">${item.quantity}</span> <button class="quantity-btn increase-qty" data-id="${item.id}"><i class="fas fa-plus"></i></button> </div> </div> <button class="cart-item__remove-btn" data-id="${item.id}"><i class="fas fa-times"></i></button> `; cartItemsContainer.appendChild(cartItemDiv); gsap.to(cartItemDiv, { opacity: 1, y: 0, scaleY: 1, duration: 0.4, delay: index * 0.05, ease: "power2.out", }); } else { const quantitySpan = cartItemDiv.querySelector(".cart-item__quantity"); const priceSpan = cartItemDiv.querySelector(".cart-item__price"); if (parseInt(quantitySpan.textContent) !== item.quantity) { gsap .timeline() .to(quantitySpan, { scale: 1.3, opacity: 0.6, duration: 0.1, ease: "power1.out", }) .set(quantitySpan, { textContent: item.quantity }) .to(quantitySpan, { scale: 1, opacity: 1, duration: 0.15, ease: "back.out(1.5)", }); } priceSpan.textContent = `$${itemTotal}`; } }); } function updateCartCount() { const totalItems = cart.reduce((sum, item) => sum + item.quantity, 0); const prevCount = parseInt(navCartCount.dataset.prevCount || "0"); navCartCount.textContent = totalItems; if (totalItems !== prevCount) { gsap.fromTo( navCartCount, { scale: 1.7, opacity: 0, y: -5 }, { scale: 1, opacity: 1, y: 0, duration: 0.45, ease: "elastic.out(1.1, 0.55)", } ); } navCartCount.dataset.prevCount = totalItems; } function updateCartTotal() { const total = cart.reduce( (sum, item) => sum + item.price * item.quantity, 0 ); cartTotalAmount.textContent = `$${total.toFixed(2)}`; } function updateCheckoutButtonState() { checkoutBtn.disabled = cart.length === 0; } function handleCartActions(event) { const target = event.target.closest("button"); if (!target) return; const productId = target.dataset.id; const itemIndex = cart.findIndex((i) => i.id === productId); if (itemIndex === -1 && !target.classList.contains("cart-item__remove-btn")) return; if (target.classList.contains("increase-qty")) { cart[itemIndex].quantity++; } else if (target.classList.contains("decrease-qty")) { cart[itemIndex].quantity--; if (cart[itemIndex].quantity <= 0) { cart.splice(itemIndex, 1); } } else if (target.classList.contains("cart-item__remove-btn")) { if (itemIndex !== -1) cart.splice(itemIndex, 1); } updateCart(); } function openCartPanel() { cartOverlay.style.visibility = "visible"; gsap.to(cartOverlay, { opacity: 1, duration: 0.45, ease: "power2.out", }); gsap.to(cartPanel, { right: 0, duration: 0.6, ease: "expo.out", onStart: updateCart, }); document.body.style.overflow = "hidden"; } function closeCartPanel() { gsap.to(cartPanel, { right: "-100%", duration: 0.5, ease: "expo.in", }); gsap.to(cartOverlay, { opacity: 0, duration: 0.4, ease: "power2.in", onComplete: () => { cartOverlay.style.visibility = "hidden"; document.body.style.overflow = ""; }, }); } productsGrid.addEventListener("click", handleAddToCart); navCartIconWrapper.addEventListener("click", openCartPanel); closeCartBtn.addEventListener("click", closeCartPanel); cartOverlay.addEventListener("click", closeCartPanel); cartItemsContainer.addEventListener("click", handleCartActions); checkoutBtn.addEventListener("click", () => { if (cart.length > 0) { alert( `Let the festivities begin with $${cartTotalAmount.textContent}!\n(Festive Cart Demo)` ); // Simulate confetti from checkout button for fun const btnRect = checkoutBtn.getBoundingClientRect(); const originX = (btnRect.left + btnRect.width / 2) / window.innerWidth; const originY = (btnRect.top + btnRect.height / 2) / window.innerHeight; confetti({ particleCount: 150, spread: 100, origin: { x: originX, y: originY }, colors: confettiColors, scalar: 1.2, }); cart = []; updateCart(); closeCartPanel(); } }); renderProducts(); updateCart(); });

Download Source Code

Get the complete source code for this tutorial to use in your projects.

Comments (0)

No comments yet. Be the first to comment!