Нет времени объяснять! Пишем таймер обратного отсчёта на чистом CSS
Ради фана реализуем динамический виджет таймера на одних стилях. Для порядка приводим ещё и решение на JavaScript (но это не главное).
CSS – мощный инструмент современного разработчика. Он многое умеет, в нём есть переменные, функции, наследование и ещё много крутых штук. Но всё-таки это не язык программирования – у него совсем другая сфера ответственности. Тем интереснее использовать CSS для решения задач программирования :) Именно этим сегодня и займёмся.
Дисклеймер! Многие вещи в принципе невозможно сделать на CSS. Ещё больше вещей делать на CSS нерационально. Мы занимаемся этим только из болезненного любопытства и стремления познать все скрытые возможности инструмента.
Условие задачи
Нужно сделать таймер обратного отсчёта. Предъявляемые требования:
- Таймер должен выводить миллисекунды от 99 до 0.
- Когда остаётся меньше 10 миллисекунд, нужно выводить только одну цифру (от 9 до 0) и центрировать её.
- Бонус #1: Цвет шрифта и фона можно настраивать в процессе работы (без кусочка JS не обойтись).
- Бонус #2. После остановки таймера его можно перезапустить.
- Код должен работать и на ПК, и на мобильных устройствах.
Чтобы выполнить указанные условия, пойдём напролом. Все нужные цифры (от 0 до 9) запишем прямо в базовую разметку страницы. Затем для имитации таймера анимируем их в нужном ритме и правильной последовательности.
Да, не очень элегантно. Но сработает, вот увидите.
Что нам потребуется?
- CSS трансформации
- CSS анимации
- Flexbox-модель
- CSS переменные
- Различные селекторы
Вот что получится в итоге:
Реализация на JavaScript
Сразу посмотрим, как это можно было сделать на JS.
Простой и понятный код, состоящий из пары функций. Для обновления таймера подписываемся на момент перерисовки браузера с помощью метода window.requestAnimationFrame().
Всего пара блоков в HTML:
И элементарные стили для выравнивания:
Согласитесь, ровным счётом ничего интересного. Поэтому бросаем эту ерунду и идём писать по-настоящему крутой таймер.
Общий подход
Сложно придумать более прямое решение, чем простое перечисление в HTML всех цифр. Расположим их в две группы (два разряда). По мере необходимости будем скрывать ненужные символы.
Анимация будет заключаться в простом прокручивании каждого блока по вертикали. В каждый момент времени в каждой колонке будет отображаться только одна цифра.
CSS трансформации
Большинство CSS-свойств плохо подходят для анимирования, так как их изменение вызывает перерисовку страницы. Но есть два «безопасных» свойства, которыми мы можем воспользоваться: transform
и opacity
.
Подробнее об анимации в вебе можно прочитать в замечательном руководстве High Performance Animations.
Для оживления таймера возьмём надёжное свойство translateY
. Оно обеспечит перемещение блока только по y
-оси.
Можно воспользоваться и полным свойством translate
, но помните, что его первый аргумент соответствует x
-координате. Если хотите перемещать элемент только по вертикали, то передайте первым параметром 0
.
Чтобы лучше понять функции трансформации, загляните в спецификацию CSS Transforms Module Level 1. Там всё разобрано на подробных примерах, так что вы разберётесь, даже если не очень любите математику.
CSS animations
Следующий шаг – анимировать применение трансформаций. Для этого мы используем CSS-анимации.
Самое важное правило, которое вы должны знать, – это @keyframes. Оно позволяет разбить анимацию на кадры и описать каждый из них в отдельности.
Здесь мы создали две анимации – по одной для каждого блока с цифрами.
Обратите внимание на два последних кадра в анимации первого блока. В этот момент там должна отображаться цифра 0
, но она нам не нужна, поэтому скрываем её с помощью width: 0
.
Чтобы применить анимации, используем краткий синтаксис свойства animation
:
Если вы зайдете в панель инструментов разработчика и откроете вкладку с вычисленными значениями (computed), то увидите, что вместо одного свойства animation
к элементу применились сразу несколько:
animation-name
Имя анимации, использующееся для её идентификации. Для него можно использовать латинские буквы, цифры, нижнее подчёркивание и дефисы. Первой должна идти буква. В начале не могут стоять зарезервированные слова none
, unset
, initial
или inherit
, а также сочетание --
. Регистр символов имеет значение.
animation-duration
Продолжительность одного цикла анимации. Для первой колонки цифр анимация будет длиться 10 секунд (10s
). Вторая колонка двигается в 10 раз быстрее (1s
).
animation-iteration-count
Количество циклов анимации, которое должно выполниться до ее остановки. Первую колонку нужно прокрутить лишь один раз – от 9 до 0. Вторую – целых 10 раз, по одному на каждое положение первой колонки.
animation-timing-function
Это свойство описывает прогресс анимации в течение одного цикла. Если вы знакомы с кривыми Безье, то можете контролировать его до мельчайших подробностей с помощью функции cubic-bezier()
. Для простых смертных есть несколько готовых значений animation-timing-function, обозначенных ключевыми словами (ease
, ease-in
и т.д)
Нам же больше подойдёт значение step-end
. Это то же самое, что и steps(1, jump-end)
.
Функция steps()
разбивает анимацию на равные «шаги», то есть величина изменяется не плавно, а прерывисто. Первый аргумент – количество шагов, второй – момент, когда начинается анимация. Ключевое слово jump-end
означает, что анимация запускается в конце, а не начале каждого шага.
Чтобы лучше разобраться в этой функции, обратитесь к статье Дэна Уилсона Jumps: The New Steps() in Web Animation.
animation-fill-mode
Состояние целевого объекта до и после завершения анимации. Нам требуется, чтобы колонки останавливались на последней цифре (последний ключевой кадр), поэтому используем значение forwards
.
Когда анимация остановится, первая цифра будет скрыта, а вторая колонка замрёт на позиции -9em
.
Ещё больше о CSS анимациях вы можете узнать в спецификации CSS Animations Level 1.
Flexbox
Цифру 0 в первом разряде мы уже скрыли с помощью инструкции @keyframes
, осталось только выровнять таймер по центру страницы:
Для вертикального выравнивания используем автоматический расчёт маргинов у потомка флекс-контейнера. Другими словами, назначаем display: flex
родительскому блоку, и margin: auto
дочернему.
Горизонтальное выравнивание достигается обычным text-align: center
.
Больше информации о Flex-модели – в спецификации CSS Flexible Box Layout Module Level 1.
Бонус #1: Динамическое изменение цвета
В условиях задачи была также бонусная возможность кастомизации цвета текста и фона нашего таймера.
Используем для ее решения кастомные свойства CSS и инпут выбора цвета.
Положим цвета в переменные:
И используем их в таймере:
Добавим на страницу два инпута для изменения цветовой схемы:
Теперь придётся написать пару строк JS-кода, чтобы динамически изменять переменные цветов при изменении инпутов:
Да, не всё можно решить на чистом CSS.
Бонус #2: Перезапуск таймера
Сейчас таймер отрабатывает только один раз. Чтобы запустить его сначала, необходимо перезагрузить страницу. Давайте добавим возможность перезапуска.
Возможно, вы подумали, что нам придётся снова смошенничать и добавить JavaScript? А вот и нет, мы справимся своими силами!
Воспользуемся чекбоксом, который обладает состоянием checked
. Мы легко можем получить к нему доступ из CSS.
Теперь анимация работает только при включенном чекбоксе, а при выключенном таймер сбрасывается и возвращается в исходное состояние.
Важно, чтобы чекбокс находился на одном уровне с таймером и стоял в разметке перед ним. Это даст нам возможность воспользоваться селектором "сиблингов" (~
). А лейбл для него может находиться где угодно.
Мы успешно завершили наш небольшой CSS only эксперимент. Вряд ли вы будете решать подобную задачу такими методами в реальной жизни, однако, нестандартный ход мыслей всегда пригодится.
Не ограничивайте себя общепринятыми рамками, копайте глубже, ищите разные подходы, изучайте инструменты, которыми вы пользуетесь.
Если вы любите CSS, вот ещё кое-что:
- Большая подборка лучших ресурсов для изучения CSS
- My heart will beat on! Сердце на CSS для неисправимых романтиков
- Верстать быстро и красиво: 15 популярных CSS фреймворков