Корзина товаров — один из самых главных компонентов любого интернет-магазина. Корзина показывает все выбранные товары в одном месте, считает скидку и общую стоимость заказа. В этой статье я покажу, как создать корзину на JS. В начале мы сверстаем все компоненты корзины, затем напишем логику на JavaScript.
Статья получилась большая, поэтому код урезанный. В корзине присутствует только самый основной функционал: кнопка вызова корзины, просмотр/добавление/удаление товаров, расчет стоимости и скидки. Про все остальное напишу уже в рамках другой статьи.
Полезные ссылки
- Пример работы корзины
- Для проекта нам нужны сами товары, чтобы было что складывать в корзину. В предыдущем уроке мы разбирали создание карточек товаров. Собственно корзину будем делать на основе кода из прошлого урока.
- Скачать исходники из данного урока вы можете с моего GitHub. Ну а объяснение кода найдете только в этой статье 🙂
Верстка компонентов корзины
Корзина состоит из двух компонентов — кнопки для вызова окна, а также всплывающего окно самой корзины.
Кнопка корзины
Представляет из себя круглую кнопку с изображением корзины. В правом верхнем углу находится счетчик выбранных товаров. Обязательно нужно сделать кнопку фиксированной, чтобы пользователь ее постоянно видел при прокрутке страницы.
Разметка для кнопки корзины:
<button class="cart" id="cart">
<img class="cart__image" src="./images/cart.png" alt="Cart" />
<div class="cart__num" id="cart_num">0</div>
</button>
Стили для кнопки корзины:
.cart {
width: 75px;
height: 75px;
border-radius: 50%;
background-color: #364364;
transition: 0.1s;
cursor: pointer;
position: fixed; /* Фиксированное расположение */
top: 50px; /* в правом верхнем углу */
right: 50px;
display: flex;
justify-content: center;
align-items: center;
object-fit: contain;
padding: 15px;
box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.1);
}
/* Увеличиваем кнопку при наведении на нее */
.cart:hover {
transform: scale(1.1);
}
/* Стилизуем счетчик товаров */
.cart__num {
position: absolute;
background-color: #d62240;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
font-size: 18px;
font-weight: 500;
top: -5px;
right: -5px;
}
Окно оформления заказа
Представляет из себя список выбранных товаров с ценами и кнопками удалить. Внизу находится информация по стоимости заказа и скидки.
Разметка окна оформления заказа:
<div class="popup">
<div class="popup__container" id="popup_container">
<div class="popup__item">
<h1 class="popup__title">Оформление заказа</h1>
</div>
<div class="popup__item" id="popup_product_list">
<div class="popup__product">
<div class="popup__product-wrap">
<img
src="./images/iphone-14-pro-max-gold.png"
alt="Apple IPhone 14 Pro Max 256Gb"
class="popup__product-image"
/>
<h2 class="popup__product-title">
Смартфон Apple IPhone 14 Pro Max 256Gb, золотой
</h2>
</div>
<div class="popup__product-wrap">
<div class="popup__product-price">135000</div>
<button class="popup__product-delete">✖</button>
</div>
</div>
</div>
<div class="popup__item">
<div class="popup__cost">
<h2 class="popup__cost-title">Итого</h2>
<output class="popup__cost-value" id="popup_cost">150000</output>
</div>
<div class="popup__cost">
<h2 class="popup__cost-title">Скидка</h2>
<output class="popup__cost-value" id="popup_discount">15000</output>
</div>
<div class="popup__cost">
<h2 class="popup__cost-title">Итого со скидкой</h2>
<output class="popup__cost-value" id="popup_cost_discount"
>135000</output
>
</div>
</div>
<button class="popup__close" id="popup_close">✖</button>
</div>
</div>
Стили окна оформления заказа:
.popup {
position: fixed;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.33); /* Небольшое затемнение на фоне */
z-index: 1000; /* Поверх всех окон */
width: 100%;
height: 100%;
display: none; /* По умолчанию скрываем окно */
justify-content: center;
align-items: center;
user-select: none;
overflow-y: scroll;
}
/* Стилизация контейнера окна */
.popup__container {
display: flex;
flex-direction: column;
justify-content: space-between; /* Распределяем элементы внутри равномерно */
width: 100%;
max-width: 800px;
min-height: 300px;
background-color: #fff;
box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.1);
border-radius: 4px;
position: relative;
}
/* Элементы окна (заголовок, список товаров, вывод стоимости) */
.popup__item {
border-bottom: 1px solid #ddd;
padding: 20px;
}
.popup__item:last-of-type {
border-bottom: none;
}
.popup__title {
font-size: 20px;
}
/* Стили для вывода товара */
.popup__product {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
}
.popup__product-wrap {
display: flex;
align-items: center;
}
.popup__product-image {
width: 100px;
height: 100px;
object-fit: contain;
margin-right: 10px;
}
.popup__product-title {
max-width: 300px;
font-weight: 500;
}
.popup__product-price {
font-size: 18px;
margin-right: 15px;
}
/* Стили для кнопки удаления товара */
.popup__product-delete {
font-size: 12px;
padding: 5px;
cursor: pointer;
color: #d62240;
}
/* Стили для вывода стоимости товаров */
.popup__cost {
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 5px;
}
.popup__cost-title {
margin-right: 15px;
font-size: 20px;
color: #364364;
text-align: right;
}
.popup__cost-value {
font-size: 20px;
}
/* Стили для кнопки закрытия окна */
.popup__close {
position: absolute;
cursor: pointer;
top: 0;
right: 0;
padding: 20px;
color: rgba(54, 67, 100, 0.7);
font-size: 20px;
}
.popup__close:hover {
color: rgb(54, 67, 100);
}
.popup--open {
display: flex;
}
Скрипты для корзины
Утилиты
В начале напишем две вспомогательные функции: toNum и toCurrency.
function toNum(str) {
const num = Number(str.replace(/ /g, ""));
return num;
}
function toCurrency(num) {
const format = new Intl.NumberFormat("ru-RU", {
style: "currency",
currency: "RUB",
minimumFractionDigits: 0,
}).format(num);
return format;
}
Функция toNum принимает строковый параметр и удаляет в нем все пробелы, затем преобразует его в число с помощью встроенной функции Number. Эта функция полезна для преобразования строк цен в числовые значения, которые можно использовать для расчетов.
Функция toCurrency форматирует число в строку валюты с использованием символа российского рубля.
Считываем элемент корзины и попапа
Считываем все элементы корзины:
const cardAddArr = Array.from(document.querySelectorAll(".card__add"));
const cartNum = document.querySelector("#cart_num");
const cart = document.querySelector("#cart");
Считываем все элементы попапа:
const popup = document.querySelector(".popup");
const popupClose = document.querySelector("#popup_close");
const body = document.body;
const popupContainer = document.querySelector("#popup_container");
const popupProductList = document.querySelector("#popup_product_list");
const popupCost = document.querySelector("#popup_cost");
const popupDiscount = document.querySelector("#popup_discount");
const popupCostDiscount = document.querySelector("#popup_cost_discount");
Обработчики кнопки открытия и закрытия корзины
Начнем с самого простого: открытие и закрытие окна корзины. Для открытия мы нажимаем круглую кнопку корзины, а для закрытия на крестик в самом окне.
Считываем нужные элементы и навешиваем обработчики на клик:
cart.addEventListener("click", (e) => {
e.preventDefault();
popup.classList.add("popup--open");
body.classList.add("lock");
});
popupClose.addEventListener("click", (e) => {
e.preventDefault();
popup.classList.remove("popup--open");
body.classList.remove("lock");
});
Класс для товара
Далее опишем класс для одного товара.
class Product {
imageSrc;
name;
price;
priceDiscount;
constructor(card) {
this.imageSrc = card.querySelector(".card__image").children[0].src;
this.name = card.querySelector(".card__title").innerText;
this.price = card.querySelector(".card__price--common").innerText;
this.priceDiscount = card.querySelector(".card__price--discount").innerText;
}
}
Класс Product представляет товар с некоторыми свойствами, такими как источник изображения, название, цена и цена со скидкой. У него есть конструктор, который принимает параметр card, который будет HTML-элементом, представляющим карточку продукта на веб-странице.
Конструктор инициализирует свойства объекта Product, запрашивая параметр card для определенных элементов с помощью CSS-селекторов и извлекая соответствующую информацию из этих элементов. Например, свойство imageSrc устанавливается путем получения исходного URL-адреса первого дочернего элемента с классом card__image внутри параметра card.
Класс для корзины
Класс для описания корзины покупок и расчета стоимости и скидок.
const cardAddArr = Array.from(document.querySelectorAll(".card__add"));
const cartNum = document.querySelector("#cart_num");
class Cart {
products;
constructor() {
this.products = [];
}
get count() {
return this.products.length;
}
addProduct(product) {
this.products.push(product);
}
removeProduct(index) {
this.products.splice(index, 1);
}
get cost() {
const prices = this.products.map((product) => {
return toNum(product.price);
});
const sum = prices.reduce((acc, num) => {
return acc + num;
}, 0);
return sum;
}
get costDiscount() {
const prices = this.products.map((product) => {
return toNum(product.priceDiscount);
});
const sum = prices.reduce((acc, num) => {
return acc + num;
}, 0);
return sum;
}
get discount() {
return this.cost - this.costDiscount;
}
}
Класс Cart, который представляет корзину покупок. У него есть конструктор, который инициализирует пустое свойство массива products.
Класс имеет методы для добавления и удаления товаров из корзины, которые изменяют массив products, добавляя или удаляя данный товар по указанному индексу.
Также определены три метода получения для стоимости корзины, стоимости со скидкой и скидки со скидкой. Эти методы вычисляют соответствующие значения путем перебора массива products и суммирования цен или скидок на каждый продукт.
Вспомогательная функция toNum используется для преобразования строк цен в числовые значения.
Создаем объект корзины и сохраняем его в localStorage
const myCart = new Cart();
if (localStorage.getItem("cart") == null) {
localStorage.setItem("cart", JSON.stringify(myCart));
}
const savedCart = JSON.parse(localStorage.getItem("cart"));
myCart.products = savedCart.products;
cartNum.textContent = myCart.count;
Этот код инициализирует новый экземпляр класса Cart и присваивает его переменной myCart. Затем он проверяет, есть ли какие-либо данные, хранящиеся в локальном хранилище браузера под ключом cart. Если таких данных нет, он сохраняет версию объекта myCart в формате JSON в локальном хранилище.
Далее код извлекает данные из локального хранилища под ключом «card» и преобразует их обратно в объект с помощью JSON.parse(). Затем проанализированный объект присваивается переменной savedCart.
Наконец, массив products из savedCart присваивается массиву products из myCart, а значение свойства count из myCart используется для обновления текстового содержимого элемента cartNum.
Добавляем товар в корзину
myCart.products = cardAddArr.forEach((cardAdd) => {
cardAdd.addEventListener("click", (e) => {
e.preventDefault();
const card = e.target.closest(".card");
const product = new Product(card);
const savedCart = JSON.parse(localStorage.getItem("cart"));
myCart.products = savedCart.products;
myCart.addProduct(product);
localStorage.setItem("cart", JSON.stringify(myCart));
cartNum.textContent = myCart.count;
});
});
Этот код добавляет прослушиватель событий к каждому элементу в массиве cardAddArr, который содержит элементы DOM, представляющие кнопки cardAdd на веб-странице. Когда пользователь нажимает одну из этих кнопок, вызывается функция прослушивания.
Функция прослушивателя сначала предотвращает действие события click по умолчанию, используя e.preventDefault(). Затем он находит ближайший элемент .card к нажатой кнопке, используя метод closest().
Код создает новый экземпляр продукта, используя элемент card, извлекая информацию из card для создания объекта product.
Далее код извлекает сохраненные данные корзины из localStorage и присваивает свой массив products свойству myCart.products. Затем он добавляет объект product в экземпляр myCart, используя свой метод addProduct.
После добавления товара в корзину код сохраняет обновленный объект myCart в localStorage в виде строки JSON с помощью JSON.stringify(). Наконец, он обновляет текстовое содержимое элемента cartNum, чтобы отобразить новое количество товаров в корзине, используя свойство myCart.count.
Заполнение корзины
function popupContainerFill() {
popupProductList.innerHTML = null;
const savedCart = JSON.parse(localStorage.getItem("cart"));
myCart.products = savedCart.products;
const productsHTML = myCart.products.map((product) => {
const productItem = document.createElement("div");
productItem.classList.add("popup__product");
const productWrap1 = document.createElement("div");
productWrap1.classList.add("popup__product-wrap");
const productWrap2 = document.createElement("div");
productWrap2.classList.add("popup__product-wrap");
const productImage = document.createElement("img");
productImage.classList.add("popup__product-image");
productImage.setAttribute("src", product.imageSrc);
const productTitle = document.createElement("h2");
productTitle.classList.add("popup__product-title");
productTitle.innerHTML = product.name;
const productPrice = document.createElement("div");
productPrice.classList.add("popup__product-price");
productPrice.innerHTML = toCurrency(toNum(product.priceDiscount));
const productDelete = document.createElement("button");
productDelete.classList.add("popup__product-delete");
productDelete.innerHTML = "✖";
productDelete.addEventListener("click", () => {
myCart.removeProduct(product);
localStorage.setItem("cart", JSON.stringify(myCart));
popupContainerFill();
});
productWrap1.appendChild(productImage);
productWrap1.appendChild(productTitle);
productWrap2.appendChild(productPrice);
productWrap2.appendChild(productDelete);
productItem.appendChild(productWrap1);
productItem.appendChild(productWrap2);
return productItem;
});
productsHTML.forEach((productHTML) => {
popupProductList.appendChild(productHTML);
});
popupCost.value = toCurrency(myCart.cost);
popupDiscount.value = toCurrency(myCart.discount);
popupCostDiscount.value = toCurrency(myCart.costDiscount);
}
Этот код определяет функцию popupContainerFill(), которая заполняет всплывающий контейнер информацией о продукте из корзины покупок, хранящейся в локальном хранилище.
Функция сначала извлекает данные корзины из локального хранилища, а затем сопоставляет массив продуктов корзины, чтобы создать новый массив элементов HTML, представляющих каждый продукт в корзине. Каждый элемент продукта создается путем создания вложенных элементов div, которые содержат изображение продукта, заголовок, цену и кнопку удаления.
При нажатии кнопки удаления для данного продукта запускается прослушиватель событий, который удаляет этот продукт из корзины, вызывая метод myCart.removeProduct(), обновляет информацию о корзине в локальном хранилище, а затем обновляет всплывающий контейнер с обновленными данными. информацию о корзине, снова вызвав popupContainerFill().
Наконец, функция устанавливает значение полей общей стоимости, скидки и стоимости со скидкой во всплывающем контейнере, чтобы отразить обновленную информацию о корзине.