Как сверстать карточки товара на HTML и CSS
Эта статья основана на одном из практических заданий verstaem.online.
Вы можете читать материал как обычное руководство или сначала попробовать выполнить задание самостоятельно, а затем сравнить свое решение с подробным разбором.
Перейти к заданиюВ этой статье мы создадим карточки товаров с изображением, названием, описанием, ценой и кнопкой покупки. На их примере разберем, как оформлять карточки одинаковой высоты, располагать контент внутри с помощью Flexbox, работать с изображениями и прижимать кнопку с ценой к нижней части блока.
Создаем HTML-разметку
Сначала создадим базовую HTML-разметку с помощью Emmet. Для этого достаточно ввести ! и нажать Tab. Обратите внимание на атрибут lang. Поскольку основной контент страницы написан на русском языке, здесь используем значение ru.
<!doctype html>
<html lang="ru">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Блок статистики</title>
</head>
<body>
</body>
</html>
Переходим к блоку. Начнем, конечно, с обертки, и здесь логично будет использовать список — <ul>. Список дает дополнительную семантику, позволяет скринридерам читать количество элементов. Таким образом, незрячие пользователи смогут понять, сколько у вас всего карточек.
<ul class="products">
<li class="products__item">
</li>
<li class="products__item">
</li>
<li class="products__item">
</li>
</ul>
Теперь сделаем карточку. Товары, как независимые блоки, которые можно переиспользовать, причем не только на текущем сайте, отлично подходят как тег <article>. И здесь сразу хочу коснуться темы «а почему не ссылка?». В данном случае, помимо ссылки, у нас в карточке есть и кнопка. А вставлять кнопку в ссылку или наоборот — ошибка. Но мы все равно реализуем клик по ссылке в любом месте карточки, только через CSS.
Внутри карточки мы сделаем изображение (не забудьте заполнить alt), заголовок, ссылку внутри заголовка (вот, куда ее лучше всего добавить), описание, а также нижний блок — <div>. В нижнем блоке будет цена и кнопка «купить». И выглядеть это будет так:
<article class="product">
<img src="img/1.png" alt="Nova X" class="product__pic">
<h3 class="product__title">
<a href="#" target="_blank" class="product__link">
Nova X
</a>
</h3>
<p class="product__descr">
OLED-дисплей и двойная камера
</p>
<div class="product__bottom">
<div class="product__price">
49 990 ₽
</div>
<button class="product__btn">
Купить
</button>
</div>
</article>
Точно также дублируем карточки, заполняем их нужными данными, и разметка готова.
Добавляем стили
Подключим файл стилей через <link> и зададим несколько базовых правил. Для этого задания достаточно переопределить box-sizing, сбросить отступ у <body> и подключить шрифт Inter, который используется в макете.
* {
box-sizing: border-box;
}
@font-face {
font-family: "Inter";
src: url("../fonts/InterRegular.woff2");
font-display: swap;
font-weight: 400;
}
@font-face {
font-family: "Inter";
src: url("../fonts/InterSemiBold.woff2");
font-display: swap;
font-weight: 600;
}
@font-face {
font-family: "Inter";
src: url("../fonts/InterBold.woff2");
font-display: swap;
font-weight: 700;
}
body {
margin: 0;
font-family: "Inter", sans-serif;
background: #282828;
}
Важный момент: мы подключаем одно семейство шрифтов Inter, поэтому в подключении и указан один и тот же font-family. Отличия между начертаниями сделаны засчет свойства font-weight.
Теперь нам стоит создать стили для списка, и тут нам понадобится flexbox. Растянем элементы с помощью flex: 1, но при этом добавим ограничители — min-width и max-width. Этот пример можно было бы сделать и с помощью обычной ширины, но там пришлось бы использовать функцию calc(), чтобы высчитать ширину.
.products {
display: flex;
flex-wrap: wrap;
margin: 0 auto;
padding: 20px;
max-width: 980px;
list-style: none;
gap: 20px;
}
.products__item {
flex: 1;
max-width: 300px;
min-width: 250px;
}
Далее стили карточки. Здесь есть несколько важных моментов:
- Мы задаем карточке
position: relative. В обычной ситуации это не понадобилось бы, но если мы хотим «растянуть» ссылку на всю карточку, это обязательно. - Мы задаем карточке flexbox и
flex-direction: column. Это понадобится, чтобы сделать нижний блок с ценой и кнопкой всегда внизу, вне зависимости от контента карточки. - Мы задаем карточке
min-height, чтобы все карточки имели один размер.
.product {
position: relative;
display: flex;
flex-direction: column;
border-radius: 24px;
padding: 16px;
min-height: 420px;
background: #111827;
}
Далее зададим стили внутренним элементам — изображению, заголовку, ссылке.
.product__pic {
display: block;
border-radius: 16px;
margin-bottom: 12px;
max-width: 100%;
}
.product__title {
margin: 0;
font-weight: 700;
font-size: 20px;
color: #fff;
}
.product__link {
text-decoration: none;
color: inherit;
}
Из важного — это max-width: 100% у изображения, без которого большие картинки просто не будут подстраиваться под ширину карточки, а также color: inherit у ссылки. Тут дело в том, что по умолчанию в браузере ссылки — синие. И даже несмотря на то, что ссылка находится в заголовке, по умолчанию она все равно будет синей. Поэтому мы явно говорим «возьми цвет текста у родителя, то есть отнаследуй от него цвет».
Теперь сделаем описание и нижний блок, добавив «магию» flexbox, чтобы нижний блок всегда был внизу.
.product__descr {
margin: 0;
margin-top: 8px;
font-weight: 400;
font-size: 16px;
line-height: 140%;
color: #94a3b8;
}
.product__bottom {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: auto;
gap: 20px;
}
.product__price {
font-weight: 700;
font-size: 18px;
color: #fff;
}
.product__btn {
position: relative;
z-index: 2;
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
border-radius: 12px;
padding: 10px 16px;
font-family: inherit;
font-weight: 600;
font-size: 14px;
color: #fff;
background: #3b82f6;
cursor: pointer;
transition: background 0.3s;
}
На самом деле, никакой магии тут нет. Просто любому flex-элементу можно задать auto-margin, и он создаст отступ на все доступное пустое пространство. Если текста в карточке мало — пространства будет много, если текста много — пространства мало. Все просто.
Ну и конечно, я не мог не сделать :hover состояние на кнопку, даже если в макете его нет. И еще один важный момент у кнопки — это position: relative и z-index: 2. Но про это уместнее сказать ниже.
Последним этапом будет «растяжение» ссылки на всю карточку. Здесь стоит использовать старый и проверенный метод — псевдоэлемент. Создаем псевдоэлемент, делаем его через position: absolute, а затем «растягиваем» с помощью свойства inset. Здесь мы по сути объединили в одном свойстве сразу 4 — top, bottom, left, right. А комбинация всех этих свойств со значением 0 позволяет абсолютно позиционированному элементу стать размером с ближайшего родителя, который имеет position: relative. А это, напомню, наша карточка. Поэтому растяжение ссылки работает.
.product__link::after {
content: "";
position: absolute;
inset: 0;
}
И тут как раз стоит упомянуть position и z-index у кнопки. Они добавлены намеренно. чтобы кнопка сама встала «поверх» ссылки, и мы могли на нее нажимать. В итоге получается, что клик по любому месту карточки кроме кнопки — это ссылка.
Теперь вы умеете создавать карточки товара с изображением, названием, описанием, ценой и кнопкой покупки.