Корзина товаров — один из самых главных компонентов любого интернет-магазина. Корзина показывает все выбранные товары в одном месте, считает скидку и общую стоимость заказа. В этой статье я покажу, как создать корзину на JS. В начале мы сверстаем все компоненты корзины, затем напишем логику на JavaScript.

Статья получилась большая, поэтому код урезанный. В корзине присутствует только самый основной функционал: кнопка вызова корзины, просмотр/добавление/удаление товаров, расчет стоимости и скидки. Про все остальное напишу уже в рамках другой статьи.

Полезные ссылки

Верстка компонентов корзины

Корзина состоит из двух компонентов — кнопки для вызова окна, а также всплывающего окно самой корзины.

Кнопка корзины

Представляет из себя круглую кнопку с изображением корзины. В правом верхнем углу находится счетчик выбранных товаров. Обязательно нужно сделать кнопку фиксированной, чтобы пользователь ее постоянно видел при прокрутке страницы.

Разметка для кнопки корзины:

<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>

Изображение корзины есть в исходнике проекта на GitHub

Стили для кнопки корзины:

.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.

Обратите внимание, что этот код предполагает, что класс Cart имеет свойства products и count, и что переменная cartNum представляет элемент в DOM, который отображает количество товаров в корзине.

Добавляем товар в корзину

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().

Наконец, функция устанавливает значение полей общей стоимости, скидки и стоимости со скидкой во всплывающем контейнере, чтобы отразить обновленную информацию о корзине.

 

Комментарии

6

Без регистрации и смс

  • гость:

    А можете подсказать куда нужно вставлять всплывающие окно и его стили со скриптами и как настроить кнопки в корзину что бы они работали

  • Аноним:

    А можете сделать отправку товара на почту? плиз очень надо

  • Николай:

    myCart.products = cardAddArr.forEach((cardAdd) =>…….

    По моему это избыточный код, так как ничего не возвращается для присваиванию myCart.products,

    поэтому можно было просто написать

    cardAddArr.forEach((cardAdd) =>…….

  • Дмитрий:

    Можете показать, как изменять количество добавленных товаров в самой корзине?

  • Егор:

    а можете сделать окно оформления заказа, для заполнения данных и отправки их на почту?

  • Гриша:

    Очень хорошо всё сделано