Сегодня мы сделаем фронтенд для собственного веб-приложения — планировщика задач.
В планировщиках информацию чаще всего организуют в виде карточек — с ними сразу видно, к чему относится задача, какие у неё теги, статус, дедлайн и всё остальное. Чтобы пользователю было удобно этим пользоваться, карточки красиво верстают на экране и добавляют возможность вывести какие-то отдельные по категориям. Сегодня мы сделаем то же самое: сверстаем страницу с карточками и сделаем фильтр, чтобы скрывать ненужное:
Что понадобится
Собирать страницу будем на чистом HTML и CSS — двух основных инструментах веб-разработчика. Если для вас это новая тема, будет немного сложно, поэтому почитайте перед стартом нашу подборку:
- Что такое HTML (и почему это важно)
- 7 полезных HTML-тегов, про которые мало кто знает
- Почему некоторые разработчики ругают вёрстку на «div»
- Зачем нужны переменные в CSS
- 10 полезных инструментов для создания роскошного CSS
- Одной строкой: новые CSS-команды для фронтендов
- Ещё больше полезных CSS-команд
То, что мы будем делать сегодня, — это работа фронтент-разработчика: мы будем работать над тем, что стоит на «переднем крае», то есть в браузере. А ещё есть бэкенд — то, что позволяет хранить наши задачи, давать к ним доступ по паролю, добавлять, удалять и делиться. С этим поработаем в другие разы.
Создаём страницу
Как обычно, на странице мы разместим только контент, а всю красоту будем наводить в стилях. Создаём стандартный HTML-файл, сохраняем его как index.html и добавляем в него два вложенных блока — wrapper
и flexDiv
. Первый будет отвечать за общие настройки страницы, а второй — за поведение элементов внутри неё.
После этого добавим теги — это будут простые чекбоксы, у которых мы в стилях скроем галочку и добавим красоты. Запишем всё в виде HTML-кода и откроем страницу в браузере:
<!DOCTYPE html>
<html lang="ru" >
<head>
<meta charset="UTF-8">
<title>Карточки с фильтрами</title>
</head>
<body>
<!-- общий блок для всей страницы -->
<div id="wrapper">
<!-- блок с карточками -->
<div class="flexDiv">
<!-- теги для фильтра -->
<!-- тег — это чекбокс -->
<input type="checkbox" id="work-box" checked />
<!-- подписываем его -->
<label for="work-box">
<!-- и указываем название и классы -->
<div class="tag work pos-abs">Работа</div>
</label>
<!-- делаем то же самое для двух остальных тегов -->
<input type="checkbox" id="personal-box" checked />
<label for="personal-box">
<div class="tag personal pos-abs">Личное</div>
</label>
<input type="checkbox" id="read-box" checked />
<label for="read-box">
<div class="tag read pos-abs">Почитать</div>
</label>
</div>
</div>
</body>
</html>
Теперь добавим контент с карточками — текст карточки и тег, к которому она относится. Для этого используем класс tag
, который отвечает за все теги, и один из классов с названием тега — work
, personal
или read
. Добавим новый код на страницу сразу после тегов:
<!-- карточки -->
<!-- каждая карточка — в своём блоке -->
<div class="note">
<!-- текст карточки -->
Поправить иконки в дизайн-системе
<!-- тег -->
<div class="tag work">Работа</div>
</div>
<div class="note">
Добавить анимацию при наведении на кнопки
<div class="tag work">Работа</div>
</div>
<div class="note">
Сделать возможность перетаскивания карточек
<div class="tag work">Работа</div>
</div>
<div class="note">
Сходить на лекцию по йоге
<div class="tag personal">Личное</div>
</div>
<div class="note">
Позвонить в фитнес-центр
<div class="tag personal">Личное</div>
</div>
<div class="note">
«Чапаев и Пустота» / Виктор Пелевин
<div class="tag read">Почитать</div>
</div>
<div class="note">
Atomic Habits / James Clear
<div class="tag read">Почитать</div>
</div>
<div class="note">
«Ясно, понятно» / Максим Ильяхов
<div class="tag read">Почитать</div>
</div>
<div class="note">
Добавить сортировку карточек
<div class="tag work">Работа</div>
</div>
<div class="note">
Оплатить коммуналку
<div class="tag personal">Личное</div>
</div>
<div class="note">
Искусство тренировок / Геральт из Ривии
<div class="tag read">Почитать</div>
</div>
<div class="note">
Купить шампунь
<div class="tag personal">Личное</div>
</div>
<div class="note">
Отправить код на проверку тимлиду
<div class="tag work">Работа</div>
</div>
<div class="note">
Посмотреть, вышел ли новый Javascript-фреймворк
<div class="tag work">Работа</div>
</div>
<div class="note">
Сделать брелок от домофона
<div class="tag personal">Личное</div>
</div>
На странице появилось всё, что нам нужно, но на карточки это не похоже. Всё дело в том, что у нас нет подключённых и настроенных стилей. Исправим это и постепенно сделаем всё красиво.
Подключаем стили
Если HTML задаёт то, что будет на странице, то CSS определяет, как это будет выглядеть. Чтобы подключить CSS-файл к странице, добавим в шапку такую строку:
<link rel=”stylesheet” href=”style.css”>
Теперь создадим указанный файл style.css и пропишем в нём сначала общие настройки для страницы и контента:
/* подключаем внешний шрифт */
@import url("https://fonts.googleapis.com/css2?family=Inter&display=swap");
/* общие настройки для всего */
#wrapper {
/* включаем абсолютное позиционирование */
position: absolute;
/* отступы сверху и слева */
top: 0;
left: 0;
/* высота и ширина блока */
width: 100%;
height: 100%;
/* цвет */
color: #333333;
/* шрифт */
font-family: "Inter", sans-serif;
}
/* внутренний блок */
.flexDiv {
/* включаем гибкую вёрстку */
display: flex;
position: absolute;
/* отступ сверху */
margin-top: 60px;
/* высота блока */
width: 90vw;
/* двигаем содержимое блока, если оно не помещается рядом */
flex-wrap: wrap;
/* максимальная ширина блока */
max-width: 800px;
/* отступы слева и сверху */
left: 50%;
top: 0;
/* сдвигаем влево по горизонтали */
transform: translateX(-50%);
}
Вот что у нас получилось:
- контент выровнялся по центру страницы;
- теги выбора стали в одну строку вместе с чекбоксами;
- текст разбился на карточки, хотя это сейчас и выглядит странно;
- в карточках видно, что тег прикреплён точно под текстом.
Теперь настроим внешний вид всех элементов по очереди и начнём с тегов.
Настраиваем теги
Сначала наведём порядок в тегах:
- скроем флажок чекбокса у тегов;
- настроим общие отступы;
- сдвинем теги наверху так, чтобы они не наезжали друг на друга и не слипались;
- настроим скругления;
- добавим решётку перед каждым тегом.
Для этого каждый пункт выполним в отдельных классах — мы их использовали на странице, теперь вдохнём в них жизнь. Как обычно, мы подписали каждую строку кода, чтобы было проще разобраться, что происходит в каждом стиле:
/* скрываем флажок у чекбокса */
input[type="checkbox"] {
display: none;
}
/* абсолютное позиционирование для тегов */
.pos-abs {
/* включаем абсолютное позиционирование */
position: absolute;
/* отступ сверху */
top: -50px;
/* внутренний отступ до левой и верхней границы */
margin: 8px;
margin-top: 8px !important;
}
/* для тега с личными записями */
.pos-abs.personal {
/* двигаем тег левее */
left: 102px;
}
/* для тега с рабочими записями */
.pos-abs.read {
/* двигаем тег ещё левее */
left: 208px;
}
/* общие настройки для тегов */
.tag {
/* внешние отступы */
padding: 6px 12px;
/* подгоняем размер блока под содержимое */
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
/* радиус скругления */
border-radius: 9999px;
/* отступ сверху */
margin-top: 12px;
/* запрещаем выделять мышкой этот элемент */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* добавляем хештег перед тегом */
.tag::before {
/* добавляем текст, отступ и прозрачность */
content: "#";
margin-right: 2px;
opacity: 0.3;
}
Стало лучше: теги сразу видны, они выглядят более опрятно и у них появился значок решётки. Теперь сделаем следующий шаг — раскрасим их и сделаем более заметными.
Раскрашиваем теги
Во всех системах управления задачами теги раскрашены разными цветами — так их проще находить и ориентироваться по цвету. Сделаем то же самое: добавим каждому тегу свой цвет.
Здесь нам помогут CSS-переменные: мы в одном месте задаём все цвета, которые нам нужны в проекте, а потом используем их при необходимости. В нашем случае нам нужно задать три цвета, по одному для каждого тега:
/* настройки переменных для цветов тегов */
.work,
.note:has(.work) {
/* рабочий */
--tagColor: #fcdbcf;
}
.personal,
.note:has(.personal) {
/* личный */
--tagColor: #ccdcff;
}
.read,
.note:has(.read) {
/* почитать */
--tagColor: #f9cafc;
}
/* настройки цвета фона тегов */
.work,
.personal,
.read {
/* берём значение фона из переменных */
background-color: var(--tagColor);
}
Создаём карточки
В проекте появились цвета, но до порядка ещё далеко: у нас нет карточек, которые бы помогли организовать это в единую систему.
Общая логика карточек будет такая: белый фон, есть тонкая граница со скруглением, карточки сами выравниваются друг относительно друга и отбрасывают тень. Ещё сделаем интересный эффект: пусть при наведении курсора на карточку её граница становится цвета тега. Внутри карточки используем гибкую вёрстку, чтобы она сама подстраивалась под содержимое и не обрезала слова:
/* настройки карточки */
.note {
/* цвет фона */
background-color: #ffffff;
/* граница */
border: 1px solid #ebebeb;
/* рисуем границу по периметру карточки */
box-sizing: border-box;
/* межстрочное расстояние */
line-height: 1.5;
/* радиус скругления */
border-radius: 8px;
/* рисуем тень */
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 4px;
/* скрываем всё, что не помещается в блок */
overflow: hidden;
/* включаем гибкую вёрстку */
display: flex;
/* организуем вёрстку по столбцам */
flex-direction: column;
/* выравниваем карточки по центру друг относительно друга */
justify-content: space-between;
/* анимация появления и исчезания */
transition: all 300ms ease-in-out 0s;
}
/* меняем цвет рамки карточки при наведении */
.note:hover {
border-color: var(--tagColor);
}
Карточки появились, причём именно так, как нам нужно: они сами подстраиваются в ряд по 2–3 карточки в зависимости от их размера. Но сам размер карточек неодинаковый: чем больше текста, тем больше карточка. Исправим это, пользуясь тем, что по умолчанию у нас все чекбоксы отмечены (это видно на первом скриншоте в самом начале):
/* если тег отмечен */
#work-box:checked ~ .note:has(.work),
#personal-box:checked ~ .note:has(.personal),
#read-box:checked ~ .note:has(.read) {
/* устанавливаем размеры и отступы карточек */
width: 31%;
height: 160px;
padding: 3%;
margin: 1%;
opacity: 1;
border-width: 1px;
}
Добавляем фильтры и анимацию
Последнее, что нам осталось сделать, — настроить код так, чтобы заработали фильтры по тегам. В этом нам поможет основное свойство чекбокса: если он виртуально отжат (нажали на тег, чтобы убрать его) — обнуляем все параметры карточки, чтобы она исчезла из вёрстки.
Ещё добавим пару визуальных штрихов: поменяем форму курсора при наведении на чекбокс и сделаем неотмеченные теги более бледными. Для этого тоже будем смотреть на чекбокс, отмечен он или нет:
/* если тег не отмечен */
#work-box + label div,
#personal-box + label div,
#read-box + label div {
/* ставим белый фон */
background-color: white;
/* оставляем рамку цвета изначального фона */
border: 1px solid var(--tagColor);
/* текст тоже делаем цвета изначального фона */
color: var(--tagColor);
}
/* если тег не отмечен */
#work-box ~ .note:has(.work),
#personal-box ~ .note:has(.personal),
#read-box ~ .note:has(.read) {
/* обнуляем параметры и размеры карточки, чтобы скрыть её из списка */
width: 0;
height: 0;
padding: 0;
margin: 0;
opacity: 0;
border-width: 0;
}
/* меняем курсор при наведении на тег */
label {
cursor: pointer;
}
/* делаем тег чуть бледнее при наведении */
label:hover {
opacity: 0.8;
}
Теперь у нас всё готово: при нажатии на тег он включается или выключается, а вместе с ним появляются или исчезают карточки, которые к нему относятся. При желании можно было бы сюда добавить обработку анимации и вёрстки при маленьких размерах экрана — попробуйте сделать это сами, используя медиазапросы. Про них мы писали, когда делали тёмную тему для сайта.
Посмотреть на работу фильтров на странице проекта.
<!DOCTYPE html>
<html lang="ru" >
<head>
<meta charset="UTF-8">
<title>Карточки с фильтрами</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- общий блок для всей страницы -->
<div id="wrapper">
<!-- блок с карточками -->
<div class="flexDiv">
<!-- теги для фильтра -->
<!-- тег — это чекбокс -->
<input type="checkbox" id="work-box" checked />
<!-- подписываем его -->
<label for="work-box">
<!-- и указываем название и классы -->
<div class="tag work pos-abs">Работа</div>
</label>
<!-- делаем то же самое для двух остальных тегов -->
<input type="checkbox" id="personal-box" checked />
<label for="personal-box">
<div class="tag personal pos-abs">Личное</div>
</label>
<input type="checkbox" id="read-box" checked />
<label for="read-box">
<div class="tag read pos-abs">Почитать</div>
</label>
<!-- карточки -->
<!-- каждая карточка — в своём блоке -->
<div class="note">
<!-- текст карточки -->
Поправить иконки в дизайн-системе
<!-- тег -->
<div class="tag work">Работа</div>
</div>
<div class="note">
Добавить анимацию при наведении на кнопки
<div class="tag work">Работа</div>
</div>
<div class="note">
Сделать возможность перетаскивания карточек
<div class="tag work">Работа</div>
</div>
<div class="note">
Сходить на лекцию по йоге
<div class="tag personal">Личное</div>
</div>
<div class="note">
Позвонить в фитнес-центр
<div class="tag personal">Личное</div>
</div>
<div class="note">
«Чапаев и Пустота» / Виктор Пелевин
<div class="tag read">Почитать</div>
</div>
<div class="note">
Atomic Habits / James Clear
<div class="tag read">Почитать</div>
</div>
<div class="note">
«Ясно, понятно» / Максим Ильяхов
<div class="tag read">Почитать</div>
</div>
<div class="note">
Добавить сортировку карточек
<div class="tag work">Работа</div>
</div>
<div class="note">
Оплатить коммуналку
<div class="tag personal">Личное</div>
</div>
<div class="note">
Искусство тренировок / Геральт из Ривии
<div class="tag read">Почитать</div>
</div>
<div class="note">
Купить шампунь
<div class="tag personal">Личное</div>
</div>
<div class="note">
Отправить код на проверку тимлиду
<div class="tag work">Работа</div>
</div>
<div class="note">
Посмотреть, вышел ли новый Javascript-фреймворк
<div class="tag work">Работа</div>
</div>
<div class="note">
Сделать брелок от домофона
<div class="tag personal">Личное</div>
</div>
</div>
</div>
</body>
</html>
/* подключаем внешний шрифт */
@import url("https://fonts.googleapis.com/css2?family=Inter&display=swap");
/* общие настройки для всего */
#wrapper {
/* включаем абсолютное позиционирование */
position: absolute;
/* отступы сверху и слева */
top: 0;
left: 0;
/* высота и ширина блока */
width: 100%;
height: 100%;
/* цвет */
color: #333333;
/* шрифт */
font-family: "Inter", sans-serif;
}
/* внутренний блок */
.flexDiv {
/* включаем гибкую вёрстку */
display: flex;
position: absolute;
/* отступ сверху */
margin-top: 60px;
/* высота блока */
width: 90vw;
/* двигаем содержимое блока, если оно не помещается рядом */
flex-wrap: wrap;
/* максимальная ширина блока */
max-width: 800px;
/* отступы слева и сверху */
left: 50%;
top: 0;
/* сдвигаем влево по горизонтали */
transform: translateX(-50%);
}
/* скрываем флажок у чекбокса */
input[type="checkbox"] {
display: none;
}
/* абсолютное позиционирование для тегов */
.pos-abs {
/* включаем абсолютное позиционирование */
position: absolute;
/* отступ сверху */
top: -50px;
/* внутренний отступ до левой и верхней границы */
margin: 8px;
margin-top: 8px !important;
}
/* для тега с личными записями */
.pos-abs.personal {
/* двигаем тег левее */
left: 102px;
}
/* для тега с рабочими записями */
.pos-abs.read {
/* двигаем тег ещё левее */
left: 208px;
}
/* общие настройки для тегов */
.tag {
/* внешние отступы */
padding: 6px 12px;
/* подгоняем размер блока под содержимое */
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
/* радиус скругления */
border-radius: 9999px;
/* отступ сверху */
margin-top: 12px;
/* запрещаем выделять мышкой этот элемент */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* добавляем хештег перед тегом */
.tag::before {
/* добавляем текст, отступ и прозрачность */
content: "#";
margin-right: 2px;
opacity: 0.3;
}
/* настройки переменных для цветов тегов */
.work,
.note:has(.work) {
/* рабочий */
--tagColor: #fcdbcf;
}
.personal,
.note:has(.personal) {
/* личный */
--tagColor: #ccdcff;
}
.read,
.note:has(.read) {
/* почитать */
--tagColor: #f9cafc;
}
/* настройки цвета фона тегов */
.work,
.personal,
.read {
/* берём значение фона из переменных */
background-color: var(--tagColor);
}
/* настройки карточки */
.note {
/* цвет фона */
background-color: #ffffff;
/* граница */
border: 1px solid #ebebeb;
/* рисуем границу по периметру карточки */
box-sizing: border-box;
/* межстрочное расстояние */
line-height: 1.5;
/* радиус скругления */
border-radius: 8px;
/* рисуем тень */
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 4px;
/* скрываем всё, что не помещается в блок */
overflow: hidden;
/* включаем гибкую вёрстку */
display: flex;
/* организуем вёрстку по столбцам */
flex-direction: column;
/* выравниваем карточки по центру друг относительно друга */
justify-content: space-between;
/* анимация появления и исчезания */
transition: all 300ms ease-in-out 0s;
}
/* меняем цвет рамки карточки при наведении */
.note:hover {
border-color: var(--tagColor);
}
/* если тег отмечен */
#work-box:checked ~ .note:has(.work),
#personal-box:checked ~ .note:has(.personal),
#read-box:checked ~ .note:has(.read) {
/* устанавливаем размеры и отступы карточек */
width: 31%;
height: 160px;
padding: 3%;
margin: 1%;
opacity: 1;
border-width: 1px;
}
/* если тег отмечен — ставим ему указанный цвет фона */
#work-box:checked + label div,
#personal-box:checked + label div,
#read-box:checked + label div {
/* цвет фона берём из переменной */
background-color: var(--tagColor);
color: unset;
}
/* если тег не отмечен */
#work-box + label div,
#personal-box + label div,
#read-box + label div {
/* ставим белый фон */
background-color: white;
/* оставляем рамку цвета изначального фона */
border: 1px solid var(--tagColor);
/* текст тоже делаем цвета изначального фона */
color: var(--tagColor);
}
/* если тег не отмечен */
#work-box ~ .note:has(.work),
#personal-box ~ .note:has(.personal),
#read-box ~ .note:has(.read) {
/* обнуляем параметры и размеры карточки, чтобы скрыть её из списка */
width: 0;
height: 0;
padding: 0;
margin: 0;
opacity: 0;
border-width: 0;
}
/* меняем курсор при наведении на тег */
label {
cursor: pointer;
}
/* делаем тег чуть бледнее при наведении */
label:hover {
opacity: 0.8;
}
Что дальше
В следующий раз сделаем такое: научим страницу создавать и удалять карточки, а данные хранить на сервере или у пользователя. Так шаг за шагом мы постепенно сделаем свой таск-трекер с анимациями, дедлайнами и напоминаниями. Подпишитесь, чтобы не пропустить продолжение.