Делаем свою доску задач с тегами и анимацией
medium

Делаем свою доску задач с тегами и анимацией

Так делает большинство таск-трекеров

Сегодня мы сделаем фронтенд для собственного веб-приложения — планировщика задач. 

В планировщиках информацию чаще всего организуют в виде карточек — с ними сразу видно, к чему относится задача, какие у неё теги, статус, дедлайн и всё остальное. Чтобы пользователю было удобно этим пользоваться, карточки красиво верстают на экране и добавляют возможность вывести какие-то отдельные по категориям. Сегодня мы сделаем то же самое: сверстаем страницу с карточками и сделаем фильтр, чтобы скрывать ненужное:

Делаем свою доску задач с тегами и анимацией

Что понадобится

Собирать страницу будем на чистом HTML и 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;
}

Что дальше

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

Это чистый фронтенд
То, чем мы тут занимались, — чистый фронтенд с продвинутым CSS. За такое хорошо платят в компаниях. Если интересно, приходите подучиться и работайте в этой сфере. В «Практикуме» для этого есть тренажёры, наставники и карьерный центр.
Попробовать бесплатно
Это чистый фронтенд Это чистый фронтенд Это чистый фронтенд Это чистый фронтенд

Текст:

Михаил Полянин

Редактор:

Максим Ильяхов

Художник:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Кирилл Климентьев

Соцсети:

Виталий Вебер

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
medium