Pull to refresh

Секундомер на CSS3 без картинок, скриптов и SMS

Reading time5 min
Views17K
CSS3 timer
Привет, Хабраюзер!
Я даже не знаю что хуже, что код для этого секундомера занимает ~ 1100 строк, или то что он работает только в половине браузеров.
Но если же тебе интересно как это работает, прошу под кат.

Вступление

Во-первых о браузерах. Работает этот код, из нынешних: в FireFox, Safari и Chrome. На подходе такие гиганты браузеры как IE10 и супер секретная новая Opera.
Во-вторых, хочу сразу отметить, что я буду описывать именно процесс анимации, а не рисования на CSS3. На будущее план как раз наоборот сделать очень красивый рисунок, на CSS. O да, это стало не так нудно, после того как разработчики из сафари отказались от своего старого стандарта linear-gradient'a (прощай color-stop, from, to, 0 0) и Opera наконец-то стала поддерживать radial-gradient (последнее произошло относительно давно).

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

Понеслась

Первым делом выстраиваем HTML (ниже приведен пример, только для «одометра»).

<div class="timer">
    <div class="numb tenHour">
    </div>
    <div class="numb hour">
    </div>
    <div class="numb tenMin">
    </div>
    <div class="numb min">
    </div>
    <div class="numb tenSec">
    </div>
    <div class="numb sec">
    </div>
    <div>
     :</div>
    <div class="numb tenMilisec">
    </div>
    <div class="numb milisec">
    </div>
</div>


План такой: каждому диву numb мы присваиваем псевдоэлемент с записью «0 1 2 3 4 5 6 7 8 9 0».
Почему два нуля? Анимация проходит от одного края к другому, а потом без задержки времени переходит в изначальное положение. По сути мы видим два разных нуля, но на живом примере этого не заметно.
Почему именно псевдоэлемент? Почему не написать числа прямо в див? Тут есть два плюса, во первых мы не портим вид HTML, а во вторых мы пишем это только один раз.
Получаем какой никакой CSS

.timer div
{
    float: left;
    width: 30px;
    height: 30px;
    position: relative;
}
.numb::before
{
    content:"0 1 2 3 4 5 6 7 8 9 0";
    position: absolute;
    width: 30px;             /* Так как мы поставили ширину 30px, все числа будут с новой строчки, это нам и нужно. */
    height: 360px;
    color: #334;
    text-align: center;
}
.timer div::after
{
    content: "";
    height: 360px;
    width: 1px;
    left: 0;
    background: #889;
}
.timer div:first-child::after
{
    display: none;
}


Ставим у родителя overflow: hidden и вуаля! Все, одометр готов.

Анимация

Приступим же к анимации, тут все просто:

.numb
{
   animation-delay: 0;   /* Обнуляем задержку анимации */
   animation-iteration-count: infinite;  /* Количество повторений анимации - бесконечно */
   animation-timing-function: linear;  /* отображение анимации относительно времени - плавно без рывков */
}
.tenHour {animation-duration: 1000000s;}  /* по убывающей количество секунд на анимацию */

.hour { animation-duration: 100000s;}  

.tenMin { animation-duration: 10000s;}

.min { animation-duration: 1000s;}

.tenSec { animation-duration: 100s;}

.sec { animation-duration: 10s;}

.tenMilisec { animation-duration: 1s;}

.milisec { animation-duration: .1s;}   /* вообще, разрешается использовать ms, но я решил использовать десятую чать секунды */


Теперь самое скучное, фреймы, точнее ключевые фреймы, они же «кадры».

@-keyframes timer
{
    0% {top:0}
    100% {top:-300px;}
}


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

@-keyframes timer
{
    0% {top:0}
    9% {top:0}
    10% {top:-30px}
    19% {top:-30px}
    20% {top:-60px}
    29% {top:-60px}
    30% {top:-90px}
    39% {top:-90px}
    40% {top:-120px}
    49% {top:-120px}
    50% {top:-150px}
    59% {top:-150px}
    60% {top:-180px}
    69% {top:-180px}
    70% {top:-210px}
    79% {top:-210px}
    80% {top:-240px}
    89% {top:-240px}
    90% {top:-270px}
    99% {top:-270px}
    100% {top:-300px;}
}


Все расписывать не буду, смысла в этом нет. Суть в том что мы берем 1% от 10-ти секунд, как время отведенное для перелистывания счетчика.
Поэтому для десятых — 0% 9.9% 10% 19.9% и так далее. Дальше больше, сотые — 0% 9.99%, тысячные — 0% 9.999%.

Заработало!

Теперь самое вкусное, как без JS заставить все это двигаться? Как нам имитировать onclick?
Есть несколько приемов, которые отвечают нашим требованиям.
Первый, самый старый и простой — псевдоклассы для ссылки
Я имею ввиду :active, :focus. Но это скучно, да и к тому же багнуто работает в Chrome (Что?! Может быть в IE?! Нет именно в Chrome)

Второй более веселый способ, использовать <input /> radio и checkbox.
И его псевдоелемент :cheked. Этот способ я вычеркнул, потому что нужен будет лишний элемент label для каждого <input />

Третий способ который пришел ко мне в голову — это та же ссылка, но с использованием :target.

Это не все способы которым можно прибегнуть, но когда я остановился на :target больше думать и не хотелось.

Итак код HTML:

            <a class="start" id="start" href="#start"></a>
            <a class="pause" id="pause" href="#pause"></a>
            <a class="stop" id="stop" href="#stop"></a>


Тут даже обьяснять ничего не надо.
И теперь CSS:

.start:target ~ .timerInner .numb, .pause:target ~ .timerInner .numb
{
   animation-name: timer;
}
.start:target ~ .timerInner .numb.tenSec, .pause:target ~ .timerInner .numb.tenSec
{
   animation-name: timertenSec;
}
.pause:target ~ .timerInner .sec,
.pause:target ~ .timerInner .tenMilisec
{
    animation-play-state: paused;
}
.stop:target ~ .timer .tenSec,
.stop:target ~ .timer .sec,
{
   animation-name: reset;
}


Мы ставим в качестве имени анимации те имена кеифреймов, которые давали выше.
И все начинает работать. Для кнопки Пауза тоже ставим, это для того что бы анимация не обнулялась при нажатии на нее, но анимацию нужно остановить, для этого и предусмотрен animation-play-state

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

Ок, теперь сделаем часики. Именно стрелку, циферблат нам не интересен.
HTML

                    <div class="clock">
                        <div class="line one"></div>
                        <div class="line three"></div>
                        <div class="line five"></div>
                        <div class="arrow"></div>
                    </div>


Дивы line просто обозначают вертикаль по которой мы ставим деления на минуты.
Arrow и есть наша стрелка.

CSS

.arrow
{
   animation-delay: 0;   /* Задержка ноль */
   animation-iteration-count: infinite;    /* бесконечно */
   animation-timing-function: linear;    /* без рывков */
   animation-duration: 600s;    /* 10м x 60с = 600с, время анимации */
   background:#666;
}


Запускаем стрелку аналогично с одометром.
Приводить код думаю лишнее.

В общем, финальный результат, еще раз.
Спасибо за внимание, нового я по сути ничего не рассказал, лично я этой инфой владею уже давно.
Просто кому-нибудь это, возможно, будет полезно, или просто интересно.
Tags:
Hubs:
Total votes 125: ↑111 and ↓14+97
Comments43

Articles