Creating a shopping cart using HTML, CSS, and JavaScript involves setting up a user interface for displaying products, adding products to the cart, viewing the cart, and removing products from the cart. Here’s a step-by-step guide to building a basic shopping cart:
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Online Store</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" /> <link rel="stylesheet" href="style.css" /> </head> <body> <nav> <div class="container"> <strong class="logo">Online Store</strong> <button class="cart-btn"> <i class="bi bi-cart"></i> <small class="cart-qty">0</small> </button> </div> </nav> <main> <div class="container"> <div class="products"></div> </div> </main> <div class="cart-overlay"></div> <div class="cart"> <div class="cart-header"> <i class="bi bi-arrow-right cart-close"></i> <h2>Your Cart</h2> </div> <div class="cart-body"></div> <div class="cart-footer"> <div> <strong>Total</strong> <span class="cart-total">0</span> </div> <button class="cart-clear">Clear Cart</button> <button class="checkout">Checkout</button> </div> </div> <script src="script.js"></script> </body> </html>
CSS (styles.css)
@import url("https://fonts.googleapis.com/css2?family=Jost:wght@400;500;600&display=swap"); :root { --color1: #ab4e68; --color2: #b07156; --color3: #533745; --color4: #9d9171; --color5: #c4a287; --color6: #4a646c; --color7: #333; --color8: #fff; --transition: all 0.3s ease-in-out; } * { font-family: "Jost", sans-serif; padding: 0; margin: 0; box-sizing: border-box; } body { display: grid; grid-template-rows: auto 1fr; gap: 30px; min-height: 100vh; } /* prevent body scroll when cart is open */ body:has(.show) { overflow: hidden; } button { cursor: pointer; border: none; border-radius: 3px; padding: 3px 10px; transition: var(--transition); } img { width: 100%; height: auto; display: block; } nav { background: var(--color1); color: var(--color8); padding-block: 20px; } nav > .container { display: flex; align-items: center; justify-content: space-between; } .logo { text-transform: uppercase; } .cart-btn { padding: 3px 8px; background: transparent; color: inherit; position: relative; } .cart-btn .bi { font-size: 1.5rem; } .cart-btn:hover { background: var(--color5); } .cart-qty { position: absolute; top: 0; right: 0; transform: translate(50%, -50%); background: var(--color7); padding: 0 5px; border-radius: 3px; display: none; } .cart-qty.visible { display: block; } .container { max-width: 1200px; width: 90%; margin: auto; } .products { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; } .product { text-align: center; position: relative; } .product img { height: 250px; object-fit: contain; } .product:hover img { opacity: 0.75; } .product h3 { margin-top: 10px; color: var(--color4); font-size: 1rem; } .product h5 { margin-top: 5px; color: var(--color6); } .product button { position: absolute; top: 10px; right: 10px; background: var(--color1); color: var(--color8); padding: 5px 10px; font-size: 1rem; display: flex; align-items: center; gap: 5px; opacity: 0; } .product:hover button { opacity: 1; } .product button::before { font-family: "bootstrap-icons"; font-size: 1.5rem; content: "\F23F"; } .product button:disabled::before { content: "\F23A"; } .product button:hover { background: var(--color2); } .product button:disabled { background: var(--color3); filter: brightness(1.75); } /* cart */ .cart-overlay { position: fixed; inset: 0; opacity: 0.5; visibility: hidden; cursor: pointer; background: var(--color7); transition: var(--transition); } .cart-overlay.show { visibility: visible; } .cart { position: fixed; inset-block: 0; right: 0; width: 100%; max-width: 420px; padding: 20px; display: grid; grid-template-rows: auto 1fr auto; transform: translateX(100%); transition: var(--transition); background: var(--color8); } .cart.show { transform: none; } .cart-header { position: relative; text-align: center; padding-block: 10px; border-bottom: 1px solid #ddd; } .cart-close { position: absolute; left: 0; top: 50%; transform: translateY(-50%); font-size: 1.5rem; cursor: pointer; } .cart-body { display: grid; align-content: start; gap: 20px; padding-block: 20px; overflow: auto; } /* hide footer if the cart is empty */ .cart-body:has(.cart-empty) + .cart-footer { display: none; } .cart-empty { text-align: center; padding-block: 20px; font-size: 1.25rem; color: var(--color6); } .cart-item { display: flex; gap: 10px; } .cart-item img { width: 100px; height: 100px; object-fit: contain; } .cart-item-detail { display: flex; flex-direction: column; flex: 1; } .cart-item-detail h3 { font-size: 1rem; color: var(--color4); } .cart-item-detail h5 { color: var(--color6); } .cart-item-amount { margin-top: auto; display: flex; align-items: center; gap: 10px; font-size: 1.25rem; } .cart-item-amount .bi { cursor: pointer; opacity: 0.25; } .cart-item-amount .bi:hover { opacity: 1; } .cart-item-amount .qty { width: 2rem; text-align: center; } .cart-item-price { margin-left: auto; } .cart-footer { border-top: 1px solid #ddd; padding-block: 10px; display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; } .cart-footer div { grid-column: 1 / -1; display: flex; align-items: center; justify-content: space-between; font-size: 1.25rem; } .cart-footer strong { font-weight: 500; color: var(--color4); } .cart-footer button { padding-block: 10px; text-transform: uppercase; } .cart-clear { background: var(--color7); color: var(--color8); } .cart-clear:hover { filter: brightness(1.75); } .checkout { background: var(--color1); color: var(--color8); } .checkout:hover { background: var(--color2); }
JavaScript (script.js)
let products = []; let cart = []; //* selectors const selectors = { products: document.querySelector(".products"), cartBtn: document.querySelector(".cart-btn"), cartQty: document.querySelector(".cart-qty"), cartClose: document.querySelector(".cart-close"), cart: document.querySelector(".cart"), cartOverlay: document.querySelector(".cart-overlay"), cartClear: document.querySelector(".cart-clear"), cartBody: document.querySelector(".cart-body"), cartTotal: document.querySelector(".cart-total"), }; //* event listeners const setupListeners = () => { document.addEventListener("DOMContentLoaded", initStore); // product event selectors.products.addEventListener("click", addToCart); // cart events selectors.cartBtn.addEventListener("click", showCart); selectors.cartOverlay.addEventListener("click", hideCart); selectors.cartClose.addEventListener("click", hideCart); selectors.cartBody.addEventListener("click", updateCart); selectors.cartClear.addEventListener("click", clearCart); }; //* event handlers const initStore = () => { loadCart(); loadProducts("https://fakestoreapi.com/products") .then(renderProducts) .finally(renderCart); }; const showCart = () => { selectors.cart.classList.add("show"); selectors.cartOverlay.classList.add("show"); }; const hideCart = () => { selectors.cart.classList.remove("show"); selectors.cartOverlay.classList.remove("show"); }; const clearCart = () => { cart = []; saveCart(); renderCart(); renderProducts(); setTimeout(hideCart, 500); }; const addToCart = (e) => { if (e.target.hasAttribute("data-id")) { const id = parseInt(e.target.dataset.id); const inCart = cart.find((x) => x.id === id); if (inCart) { alert("Item is already in cart."); return; } cart.push({ id, qty: 1 }); saveCart(); renderProducts(); renderCart(); showCart(); } }; const removeFromCart = (id) => { cart = cart.filter((x) => x.id !== id); // if the last item is remove, close the cart cart.length === 0 && setTimeout(hideCart, 500); renderProducts(); }; const increaseQty = (id) => { const item = cart.find((x) => x.id === id); if (!item) return; item.qty++; }; const decreaseQty = (id) => { const item = cart.find((x) => x.id === id); if (!item) return; item.qty--; if (item.qty === 0) removeFromCart(id); }; const updateCart = (e) => { if (e.target.hasAttribute("data-btn")) { const cartItem = e.target.closest(".cart-item"); const id = parseInt(cartItem.dataset.id); const btn = e.target.dataset.btn; btn === "incr" && increaseQty(id); btn === "decr" && decreaseQty(id); saveCart(); renderCart(); } }; const saveCart = () => { localStorage.setItem("online-store", JSON.stringify(cart)); }; const loadCart = () => { cart = JSON.parse(localStorage.getItem("online-store")) || []; }; //* render functions const renderCart = () => { // show cart qty in navbar const cartQty = cart.reduce((sum, item) => { return sum + item.qty; }, 0); selectors.cartQty.textContent = cartQty; selectors.cartQty.classList.toggle("visible", cartQty); // show cart total selectors.cartTotal.textContent = calculateTotal().format(); // show empty cart if (cart.length === 0) { selectors.cartBody.innerHTML = '<div class="cart-empty">Your cart is empty.</div>'; return; } // show cart items selectors.cartBody.innerHTML = cart .map(({ id, qty }) => { // get product info of each cart item const product = products.find((x) => x.id === id); const { title, image, price } = product; const amount = price * qty; return ` <div class="cart-item" data-id="${id}"> <img src="${image}" alt="${title}" /> <div class="cart-item-detail"> <h3>${title}</h3> <h5>${price.format()}</h5> <div class="cart-item-amount"> <i class="bi bi-dash-lg" data-btn="decr"></i> <span class="qty">${qty}</span> <i class="bi bi-plus-lg" data-btn="incr"></i> <span class="cart-item-price"> ${amount.format()} </span> </div> </div> </div>`; }) .join(""); }; const renderProducts = () => { selectors.products.innerHTML = products .map((product) => { const { id, title, image, price } = product; // check if product is already in cart const inCart = cart.find((x) => x.id === id); // make the add to cart button disabled if already in cart const disabled = inCart ? "disabled" : ""; // change the text if already in cart const text = inCart ? "Added in Cart" : "Add to Cart"; return ` <div class="product"> <img src="${image}" alt="${title}" /> <h3>${title}</h3> <h5>${price.format()}</h5> <button ${disabled} data-id=${id}>${text}</button> </div> `; }) .join(""); }; //* api functions const loadProducts = async (apiURL) => { try { const response = await fetch(apiURL); if (!response.ok) { throw new Error(`http error! status=${response.status}`); } products = await response.json(); console.log(products); } catch (error) { console.error("fetch error:", error); } }; //* helper functions const calculateTotal = () => { return cart .map(({ id, qty }) => { const { price } = products.find((x) => x.id === id); return qty * price; }) .reduce((sum, number) => { return sum + number; }, 0); }; Number.prototype.format = function () { return this.toLocaleString("en-US", { style: "currency", currency: "USD", }); }; //* initialize setupListeners();
Explanation
- HTML Structure:
- The HTML includes containers for products and the shopping cart, along with a heading and a total price display.
- CSS Styling:
- The CSS styles the container, products, and cart items for a clean, modern look.
- Products and cart items are styled with a border and some padding.
- Buttons are styled for a consistent look with hover effects.
- JavaScript Functionality:
- The JavaScript initializes an array of products and an empty cart array.
- Functions to display products, add to cart, remove from cart, and update the total price are defined.
- Event listeners are set up for adding and removing items from the cart.
- The
displayProducts
function creates HTML elements for each product and adds them to the products container. - The
displayCart
function creates HTML elements for each cart item and adds them to the cart container, and also updates the total price. - The
addToCart
function adds a product to the cart array and updates the display. - The
removeFromCart
function removes a product from the cart array and updates the display. - The
updateTotal
function calculates the total price of items in the cart and updates the total display.
This setup provides a simple and functional shopping cart. You can expand it by adding more features like quantity selection, saving the cart to local storage, or integrating with a backend server for persistent data storage.
Reviews
There are no reviews yet. Be the first one to write one.