Мы уже много говорили о базовом 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-графики и потом используем её для анимации.
Для этого мы поступим так:
- Используем тег <label>, чтобы потом можно было подписать чекбокс, и сразу напишем id элемента, который нужно будет подписать. Этим элементом станет чекбокс.
- Внутри этого тега разместим тег <input>, который создаст нам чекбокс.
- Укажем для чекбокса стиль, чтобы настроить его внешний вид, и id, чтобы подпись поняла, к чему именно она относится.
- Ниже добавим SVG-область, в которой нарисуем сразу рамку и галочку.
- Рамка будет видимой сразу, а видимостью галочки мы будем управлять через стили.
- Добавим саму подпись чекбокса в теге <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 > * + *
, и вот что она означает:
- Символ «больше» означает, что правило будет относиться к каким-то элементам, которые лежат внутри тега с классом .check-group.
- Звёздочка означает, что это правило относится ко всем элементам, которые лежат внутри.
- Плюс означает, что предыдущее правило применяется, только если предыдущий (то есть любой вложенный) и следующий (то есть тоже любой вложенный) элементы идут друг за другом.
- Вторая звёздочка означает, что следующий элемент тоже может быть любым.
Если перевести это всё с айтишного на русский, то будет звучать примерно так:
«Отодвинь все вложенные элементы друг от друга на такое-то расстояние. От верха с низом не отодвигай, потому для них это правило не работает».
Ради интереса можете удалить плюс и вторую звёздочку и посмотреть, как изменится внешний вид группы.
Считаем количество отмеченных галочек
Если вы смотрели 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;
}