Делаем красивые чекбоксы с анимацией

Делаем красивые чекбоксы с анимацией

Погружаемся в хитрости современного CSS

Мы уже много говорили о базовом CSS, немного о препроцессорах, современных командах и гридах. Теперь посмотрим на вложенности и зависимости — полезный инструмент для веб-разработки в реальном мире. 

Основой нашего кода станет вот этот кусок, в частности — знак плюса:

.checkbox__input:checked + .checkbox__icon .tick {...}

Плюс означает, что стиль применяется к элементам класса .checkbox__icon .tick, которые идут в HTML-коде сразу после отмеченного элемента checkbox__input.

Также в коде будет использоваться такое заклинание: 

.check-group > * + * {...}

Но его так запросто не объяснишь, листайте дальше. 

Что делаем

Допустим, мы делаем интерфейс интернет-пиццерии и хотим добавить конфигуратор начинки. Нам нужно, чтобы пользователь мог поставить галочки возле ингредиентов. При этом важно, чтобы всё выглядело красиво (а не как в стандартном исполнении браузера). 

Классический дизайн чекбоксов

Если мы сделаем это просто на чекбоксах, мы получим мелкие и едва кликабельные штучки. Вот их сейчас мы будем прокачивать, чтобы они были крупными и анимированными: 

Делаем красивые чекбоксы с анимацией

<!DOCTYPE html>
<html lang="en" >
<head>
  <meta charset="UTF-8">
  <title>Начинка для пиццы</title>

</head>
<body>

<h1>
Что добавить в пиццу?
</h1>
<p>
<input type="checkbox" name="in1"> Оливки </input>
</p>
<p>
<input type="checkbox" name="in2"> Пепперони </input>
</p>
<p>
<input type="checkbox" name="in3"> Каперсы </input>
</p>
<p>
<input type="checkbox" name="in4"> Помидоры </input>
</p>
</body>
</html>

Подключаем нормализатор и файл со стилями

Чтобы чекбоксы выглядели одинаково во всех браузерах, начнём с нормализатора CSS. Это специальный набор стилей, который приводит содержимое нашей страницы к единому виду в каждом браузере. 

Подключается нормализатор так:

<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">

Заодно сразу подключим файл со стилями. Он пока пустой:

<link rel="stylesheet" href="style.css">

Делаем красивые чекбоксы с анимацией
Из-за нормализатора сломались поля, но это не главная наша проблема

Меняем внешний вид чекбокса

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

Для этого мы поступим так:

  1. Используем тег <label>, чтобы потом можно было подписать чекбокс, и сразу напишем id элемента, который нужно будет подписать. Этим элементом станет чекбокс. 
  2. Внутри этого тега разместим тег <input>, который создаст нам чекбокс. 
  3. Укажем для чекбокса стиль, чтобы настроить его внешний вид, и id, чтобы подпись поняла, к чему именно она относится. 
  4. Ниже добавим SVG-область, в которой нарисуем сразу рамку и галочку.
  5. Рамка будет видимой сразу, а видимостью галочки мы будем управлять через стили.
  6. Добавим саму подпись чекбокса в теге <span>.

<!DOCTYPE html>
<html lang="ru" >
<head>
  <meta charset="UTF-8">
  <title>Начинка для пиццы</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
  <link rel="stylesheet" href="style.css">

</head>
<body>

<h1>
Что добавить в пиццу?
</h1>

<!-- создаём подпись и указываем, к какому элементу она относится -->
  <label for="myCheckbox01" class="checkbox">

    <!-- размещаем обычный чекбокс и задаём ему нужный класс -->
    <!-- также указываем id, чтобы подпись смогла найти этот инбокс -->
    <input class="checkbox__input" type="checkbox" id="myCheckbox01">
    
    <!-- готовим виртуальное пространство для рисования -->
    <svg class="checkbox__icon" viewBox="0 0 22 22">
      <!-- рисуем красивую рамку чекбокса -->
      <rect width="21" height="21" x=".5" y=".5" fill="#FFF" stroke="#006F94" rx="3" />
      <!-- сразу делаем красивую галочку, но пока делаем её невидимой -->
      <path class="tick" stroke="#6EA340" fill="none" stroke-linecap="round" stroke-width="4" d="M4 10l5 5 9-9" />
    </svg>

    <!-- подписываем наш чекбокс -->
    <span class="checkbox__label">Оливки</span>
  </label>
</body>
</html>
Делаем красивые чекбоксы с анимацией

Видно, что у нас появился новый дизайн чекбокса и галочки, но сломались размеры. Всё дело в том, что мы не настроили стили для этих элементов. Добавим это в файл style.css — читайте комментарии, если что-то будет непонятно:

/* общие настройки для всей страницы */
body {
  /* отступы */
  margin: 2em;
  /* цвет фона */
  background: #f0f0f0;
  /* добавляем адаптивное расположение элементов */
  display: flex;
  /* расстояние между элементами */
  gap: 2rem;
  /* разрешаем увеличивать размеры областей в зависимости от размеров элемента */
  flex-wrap: wrap;
  align-items: flex-start;
}

/* настройки для всего элемента с чекбоксом */
.checkbox {
  /* меняем внешний вид курсора */
  cursor: pointer;
  /* выравниваем элементы по центру */
  display: flex;
  align-items: center;
}

/* отдельные настройки для самого чекбокса */
.checkbox__input {
  /* устанавливаем абсолютное позиционирование */
  position: absolute;
  /* задаём высоту и ширину */
  width: 1.375em;
  height: 1.375em;
  /* делаем чекбокс непрозрачным, чтобы скрыть исходный элемент и заменить его потом нарисованным */
  opacity: 0;
  /* меняем внешний вид курсора */
  cursor: pointer;
}

/* настройки для SVG-иконки */
.checkbox__icon {
  /* размеры совпадают с размерами скрытого чекбокса */
  width: 1.375em;
  height: 1.375em;
  /* убираем ограничение по наименьшей ширине блока */
  flex-shrink: 0;
  /* разрешаем отображать содержимое за пределами блока */
  overflow: visible;
}

/* общие настройки для нового чекбокса и галочки */
.checkbox__icon .tick {
  /* рисовать будем всё отрезками по 20 пикселей */
  stroke-dasharray: 20px;
  /* но сместим следующие отрезки тоже на 20 пикселей, чтобы получить сплошные линии */
  stroke-dashoffset: 20px;
  /* это даст нам плавную анимацию отрисовки галочки */
  transition: stroke-dashoffset 0.2s ease-out;
}

/* настройки для подписи чекбокса */
.checkbox__label {
  /* добавляем отступ слева */
  margin-left: 0.5em;
}
Делаем красивые чекбоксы с анимацией

Стало лучше, появился новый красивый чекбокс, но галочку в него поставить пока нельзя — мы не настроили стили для этого. Исправим это, добавив новые стили в файл:

/* включаем возможность поставить галочку */
.checkbox__input:checked + .checkbox__icon .tick {
  /* убираем смещение для отрезков, чтобы включить анимацию галочки */
  stroke-dashoffset: 0;
}

👉 Обратите внимание на плюс в этой записи. Он означает, что этот стиль применяется к элементам класса .checkbox__icon .tick, которые идут в HTML-коде сразу после отмеченного элемента checkbox__input. Если между элементами с этими классами вставить какой-то другой тег, то ничего не сработает. Такой подход позволяет тонко управлять поведением элементов на странице. 

Делаем красивые чекбоксы с анимацией

Добавляем остальные чекбоксы

Теперь, когда мы научились ставить красивые чекбоксы с анимированными галочками, вернёмся в HTML-код и добавим остальные варианты для пиццы. Делаем это простым копированием и вставкой. Единственное, что нужно не забыть поменять, — числа в id у чекбоксов:

<!DOCTYPE html>
<html lang="ru" >
<head>
  <meta charset="UTF-8">
  <title>Начинка для пиццы</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
  <link rel="stylesheet" href="style.css">

</head>
<body>

<h1>
Что добавить в пиццу?
</h1>

  <!-- создаём подпись и указываем, к какому элементу она относится -->
  <label for="myCheckbox01" class="checkbox">

    <!-- размещаем обычный чекбокс и задаём ему нужный класс -->
    <!-- также указываем id, чтобы подпись смогла найти этот инбокс -->
    <input class="checkbox__input" type="checkbox" id="myCheckbox01">
    
    <!-- готовим виртуальное пространство для рисования -->
    <svg class="checkbox__icon" viewBox="0 0 22 22">
      <!-- рисуем красивую рамку чекбокса -->
      <rect width="21" height="21" x=".5" y=".5" fill="#FFF" stroke="#006F94" rx="3" />
      <!-- сразу делаем красивую галочку, но пока делаем её невидимой -->
      <path class="tick" stroke="#6EA340" fill="none" stroke-linecap="round" stroke-width="4" d="M4 10l5 5 9-9" />
    </svg>

    <!-- подписываем наш чекбокс -->
    <span class="checkbox__label">Оливки</span>
  </label>

  <label for="myCheckbox02" class="checkbox">
    <input class="checkbox__input" type="checkbox" id="myCheckbox02">
    <svg class="checkbox__icon" viewBox="0 0 22 22">
      <rect width="21" height="21" x=".5" y=".5" fill="#FFF" stroke="#006F94" rx="3" />
      <path class="tick" stroke="#6EA340" fill="none" stroke-linecap="round" stroke-width="4" d="M4 10l5 5 9-9" />
    </svg>
    <span class="checkbox__label">Пепперони</span>
  </label>

   <label for="myCheckbox03" class="checkbox">
    <input class="checkbox__input" type="checkbox" id="myCheckbox03">
    <svg class="checkbox__icon" viewBox="0 0 22 22">
      <rect width="21" height="21" x=".5" y=".5" fill="#FFF" stroke="#006F94" rx="3" />
      <path class="tick" stroke="#6EA340" fill="none" stroke-linecap="round" stroke-width="4" d="M4 10l5 5 9-9" />
    </svg>
    <span class="checkbox__label">Каперсы</span>
  </label>

   <label for="myCheckbox04" class="checkbox">
    <input class="checkbox__input" type="checkbox" id="myCheckbox04">
    <svg class="checkbox__icon" viewBox="0 0 22 22">
      <rect width="21" height="21" x=".5" y=".5" fill="#FFF" stroke="#006F94" rx="3" />
      <path class="tick" stroke="#6EA340" fill="none" stroke-linecap="round" stroke-width="4" d="M4 10l5 5 9-9" />
    </svg>
    <span class="checkbox__label">Помидоры</span>
  </label>

</body>
</html>

Делаем красивые чекбоксы с анимацией

Теперь у нас есть все нужные чекбоксы, только они стоят в ряд, а нам хорошо бы их сделать друг под другом. А ещё у нас до сих пор нет автоподсчёта отмеченных галочек.

Объединяем чекбоксы

Самый простой способ что-то объединить в HTML — использовать тег <div>. Объединим наши чекбоксы с помощью него и зададим отдельный класс для оформления:

<!-- общий блок для всех чекбоксов -->
<div class="check-group">
<!-- здесь находится код наших чекбоксов -->
</div>

А теперь пропишем стили в CSS-файле:

.check-group {
  /* делаем группе отдельный фон */
  background: #fff;
  /* ограничиваем ширину блока, чтобы он не расползался */
  max-width: 13rem;
  /* добавляем отступы */
  padding: 1.5rem;
  /* делаем красивое скругление и тень */
  border-radius: 0.5rem;
  box-shadow: 0 1px 3px rgba(0, 0, 10, 0.2);
  /* сразу добавляем два счётчика, они понадобятся для подсчёта выбранных элементов */
  counter-reset: total;
  counter-reset: checked;
}

/* делаем отступы внутри группы */
.check-group > * + * {
  margin-top: 0.75rem;
}
Делаем красивые чекбоксы с анимацией

Самая магия происходит в строке .check-group > * + *, и вот что она означает:

  1. Символ «больше» означает, что правило будет относиться к каким-то элементам, которые лежат внутри тега с классом .check-group.
  2. Звёздочка означает, что это правило относится ко всем элементам, которые лежат внутри.
  3. Плюс означает, что предыдущее правило применяется, только если предыдущий (то есть любой вложенный) и следующий (то есть тоже любой вложенный) элементы идут друг за другом.
  4. Вторая звёздочка означает, что следующий элемент тоже может быть любым.

Если перевести это всё с айтишного на русский, то будет звучать примерно так:

«Отодвинь все вложенные элементы друг от друга на такое-то расстояние. От верха с низом не отодвигай, потому для них это правило не работает».

Ради интереса можете удалить плюс и вторую звёздочку и посмотреть, как изменится внешний вид группы.

Считаем количество отмеченных галочек

Если вы смотрели CSS-код из предыдущего этапа, то обратили внимание на счётчики, которые мы задаём прямо в CSS. Современный CSS получил поддержку таких мини-переменных, которые могут считать количество отображённых элементов. 

Чтобы увеличить значение счётчика на единицу, используют команду  counter-increment. Используем её, чтобы посчитать сначала количество всех чекбоксов:

/* подсчитываем количество всех чекбоксов */
.check-group .checkbox {
  counter-increment: total;
}

А потом считаем количество отмеченных галочек:

/* считаем только отмеченные галочки */
.check-group input[type=checkbox]:checked {
  counter-increment: checked;
}

Последнее, что нам осталось сделать, — вывести на экран итоговое количество выбранных добавок.

Добавляем вывод с результатами

Чтобы вывести строку с результатом, внутри общего блока в самом конце разместим строку:

<div class="check-group__result">Выбрано:</div>

Теперь нам нужно научить станицу правильно заполнять её содержимое. Для этого используем два стиля — просто для блока с этой надписью, чтобы она выглядела солидно, и второй стиль с псевдоклассом :after, который сработает после отрисовки остальных стилей:

/* настраиваем внешний вид строки с результатом */
.check-group__result {
  /* делаем жирный шрифт */
  font-weight: bold;
  /* немного отступаем сверху */
  padding-top: 0.75rem;
  /* рисуем тонкую линию над текстом */
  border-top: 1px solid rgba(0, 0, 0, 0.2);
}

/* обрабатываем результаты */
.check-group__result:after {
  /* добавляем строку со значениями счётчиков */
  content: counter(checked) " / " counter(total);
  /* добавляем отступ слева */
  padding-left: 1ch;
}
Делаем красивые чекбоксы с анимацией

Мы получили анимацию и динамическое управление контентом на странице средствами CSS. Пока что это не слишком функционально, потому что мы не отправляем эти данные никуда на сервер и не берём с клиента деньги за пиццу, но зато CSS. Есть чем гордиться 🙂

Готовый результат можно посмотреть на странице проекта.

<!DOCTYPE html>
<html lang="ru" >
<head>
  <meta charset="UTF-8">
  <title>Начинка для пиццы</title>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
  <link rel="stylesheet" href="style.css">

</head>
<body>

<h1>
Что добавить в пиццу?
</h1>
<!-- общий блок для всех чекбоксов -->
<div class="check-group">
  <!-- создаём подпись и указываем, к какому элементу она относится -->
  <label for="myCheckbox01" class="checkbox">

    <!-- размещаем обычный чекбокс и задаём ему нужный класс -->
    <!-- также указываем id, чтобы подпись смогла найти этот инбокс -->
    <input class="checkbox__input" type="checkbox" id="myCheckbox01">
    
    <!-- готовим виртуальное пространство для рисования -->
    <svg class="checkbox__icon" viewBox="0 0 22 22">
      <!-- рисуем красивую рамку чекбокса -->
      <rect width="21" height="21" x=".5" y=".5" fill="#FFF" stroke="#006F94" rx="3" />
      <!-- сразу делаем красивую галочку, но пока делаем её невидимой -->
      <path class="tick" stroke="#6EA340" fill="none" stroke-linecap="round" stroke-width="4" d="M4 10l5 5 9-9" />
    </svg>

    <!-- подписываем наш чекбокс -->
    <span class="checkbox__label">Оливки</span>
  </label>

  <label for="myCheckbox02" class="checkbox">
    <input class="checkbox__input" type="checkbox" id="myCheckbox02">
    <svg class="checkbox__icon" viewBox="0 0 22 22">
      <rect width="21" height="21" x=".5" y=".5" fill="#FFF" stroke="#006F94" rx="3" />
      <path class="tick" stroke="#6EA340" fill="none" stroke-linecap="round" stroke-width="4" d="M4 10l5 5 9-9" />
    </svg>
    <span class="checkbox__label">Пепперони</span>
  </label>

   <label for="myCheckbox03" class="checkbox">
    <input class="checkbox__input" type="checkbox" id="myCheckbox03">
    <svg class="checkbox__icon" viewBox="0 0 22 22">
      <rect width="21" height="21" x=".5" y=".5" fill="#FFF" stroke="#006F94" rx="3" />
      <path class="tick" stroke="#6EA340" fill="none" stroke-linecap="round" stroke-width="4" d="M4 10l5 5 9-9" />
    </svg>
    <span class="checkbox__label">Каперсы</span>
  </label>

   <label for="myCheckbox04" class="checkbox">
    <input class="checkbox__input" type="checkbox" id="myCheckbox04">
    <svg class="checkbox__icon" viewBox="0 0 22 22">
      <rect width="21" height="21" x=".5" y=".5" fill="#FFF" stroke="#006F94" rx="3" />
      <path class="tick" stroke="#6EA340" fill="none" stroke-linecap="round" stroke-width="4" d="M4 10l5 5 9-9" />
    </svg>
    <span class="checkbox__label">Помидоры</span>
  </label>

  <!-- считаем и выводим результаты -->
  <div class="check-group__result">Выбрано:</div>
</div>


  
</body>
</html>

/* общие настройки для всей страницы */
body {
  /* отступы */
  margin: 2em;
  /* цвет фона */
  background: #f0f0f0;
  /* добавляем адаптивное расположение элементов */
  display: flex;
  /* расстояние между элементами */
  gap: 2rem;
  /* разрешаем увеличивать размеры областей в зависимости от размеров элемента */
  flex-wrap: wrap;
  align-items: flex-start;
}

/* настройки для всего элемента с чекбоксом */
.checkbox {
  /* меняем внешний вид курсора */
  cursor: pointer;
  /* выравниваем элементы по центру */
  display: flex;
  align-items: center;
}

/* отдельные настройки для самого чекбокса */
.checkbox__input {
  /* устанавливаем абсолютное позиционирование */
  position: absolute;
  /* задаём высоту и ширину */
  width: 1.375em;
  height: 1.375em;
  /* делаем чекбокс непрозрачным, чтобы скрыть исходный элемент и заменить его потом нарисованным */
  opacity: 0;
  /* меняем внешний вид курсора */
  cursor: pointer;
}

/* настройки для SVG-иконки */
.checkbox__icon {
  /* размеры совпадают с размерами скрытого чекбокса */
  width: 1.375em;
  height: 1.375em;
  /* убираем ограничение по наименьшей ширине блока */
  flex-shrink: 0;
  /* разрешаем отображать содержимое за пределами блока */
  overflow: visible;
}

/* общие настройки для нового чекбокса и галочки */
.checkbox__icon .tick {
  /* рисовать будем всё отрезками по 20 пикселей */
  stroke-dasharray: 20px;
  /* но сместим начало всех отрезков тоже на 20 пикселей, чтобы пока галочка не рисовалась */
  stroke-dashoffset: 20px;
  /* это даст нам плавную анимацию отрисовки галочки */
  transition: stroke-dashoffset 0.2s ease-out;
}

/* настройки для подписи чекбокса */
.checkbox__label {
  /* добавляем отступ слева */
  margin-left: 0.5em;
}

/* включаем возможность поставить галочку */
.checkbox__input:checked + .checkbox__icon .tick {
  /* убираем смещение для отрезков, чтобы включить анимацию галочки */
  stroke-dashoffset: 0;
}


.check-group {
  /* делаем группе отдельный фон */
  background: #fff;
  /* ограничиваем ширину блока, чтобы он не расползался */
  max-width: 13rem;
  /* добавляем отступы */
  padding: 1.5rem;
  /* делаем красивое скругление и тень */
  border-radius: 0.5rem;
  box-shadow: 0 1px 3px rgba(0, 0, 10, 0.2);
  /* сразу добавляем два счётчика, они понадобятся для подсчёта выбранных элементов */
  counter-reset: total;
  counter-reset: checked;
}

/* делаем отступы внутри группы */
.check-group > * + * {
  margin-top: 0.75rem;
}

/* подсчитываем количество всех чекбоксов */
.check-group .checkbox {
  counter-increment: total;
}

/* считаем только отмеченные галочки */
.check-group input[type=checkbox]:checked {
  counter-increment: checked;
}

/* настраиваем внешний вид строки с результатом */
.check-group__result {
  /* делаем жирный шрифт */
  font-weight: bold;
  /* немного отступаем сверху */
  padding-top: 0.75rem;
  /* рисуем тонкую линию над текстом */
  border-top: 1px solid rgba(0, 0, 0, 0.2);
}

/* обрабатываем результаты */
.check-group__result:after {
  /* добавляем строку со значениями счётчиков */
  content: counter(checked) " / " counter(total);
  /* добавляем отступ слева */
  padding-left: 1ch;
}

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

Текст:

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

Редактор:

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

Художник:

Даня Берковский

Корректор:

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

Вёрстка:

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

Соцсети:

Алина Грызлова

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

Чтобы программа не падала из-за разных ошибок

easy
Что означает ошибка Exception has occurred: TypeError
Что означает ошибка Exception has occurred: TypeError

Неочевидная ошибка в типах данных Python.

easy
Простейший генератор текста на цепях Маркова
Простейший генератор текста на цепях Маркова

Выдаёт любой текст на любую тему.

medium
Делаем своё расширение для браузера за 10 минут
Делаем своё расширение для браузера за 10 минут

Cнова запускаем снежинки.

medium
Решаем задачу коммивояжёра простым перебором
Решаем задачу коммивояжёра простым перебором

Простое решение, но много кода.

easy
Делаем страницу «О себе» на Бутстрапе

Если ты можешь сделать страницу о себе, ты можешь сделать всё.

medium
CSS: как задавать размеры элементов на сайте
CSS: как задавать размеры элементов на сайте

Шпаргалка по единицам измерения для начинающих верстальщиков

easy
Ваш собственный телеграм-секретарь: делаем вместе
Ваш собственный телеграм-секретарь: делаем вместе

Пошаговая инструкция для тех, кому нужен секретарь.

easy
$ is not defined в jQuery: что это значит и что делать
medium
Задача про вёрстку баннера
Задача про вёрстку баннера

Для тех, кто любит конкурсы разработчиков.

hard
medium