Прогрессбар — элемент вроде и редко встречающийся на сайтах (в отличии от селектов, чекбоксов, инпутов и прочего), но все равно без него не обойдется ни один ui-kit.

На данный момент HTML5 предоставляет нам нативный элемент progress, базовый функционал которого поддерживается практически всеми современными браузерами (caniuse.com/#feat=progress).

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

В этой статье я попытаюсь показать способ кастомизации прогрессбара, исходя из двух условий:

  1. Никакого JS. Вся стилизация делается исключительно средствами CSS;
  2. При последующей работе с кастомизированным прогрессбаром, мы должны работать исключительно с ним (менять значение и не думать о том что нужно поменять ширину ползунка или подставить правильный процент).

То есть, если мы хотим установить значение прогрессбара в 50%, мы делаем приблизительно следующее, и ничего более:

document.getElementById('progress').value = '50';

Сразу скажу что при верстке я всегда стараюсь использовать средства CSS по максимуму, на столько на сколько это возможно не прибегая к помощи JS. Так что такой способ может показаться кому-то излишним. Так же в примере будет использован препроцессор, так как без него очень долго писать нужные стили. Я предпочитаю LESS, но при написании статьи я так и не нашел ни одной вменяемой песочницы с поддержкой LESS. Так что в примере будет SCSS.

Итак, начнем с базовой HTML разметки:

<div class="progress">
    <progress max="100" value="0"></progress>
    <div class="progress-value"></div>
    <div class="progress-bg"><div class="progress-bar"></div></div>
</div>

Прячем нативный элемент:

.progress
{
  font: 12px Arial, Tahoma, sans-serif;
  position: relative;
  overflow: hidden;
}

.progress progress
{
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
  left: -777px;
}

.progress-bar
{
  overflow: hidden;
  background: #ac92ec;
  width: 0;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
}

.progress-value
{
  color: #333;
  display: block;
  line-height: 21px;
  text-align: center;
}

.progress-bg
{
  background: #e6e9ed;
  position: relative;
  height: 8px;
  border-radius: 5px;
  overflow: hidden;
}

Далее наводим красоту:

.progress-bar:after
{
  background-image: -webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
  background-image: -o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
  background-image: linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);
  -webkit-background-size: 40px 40px;
  background-size: 40px 40px;
  position: absolute;
  content: '';
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

Добавляем анимацию:

@keyframes progress_bar {
  0% {
    background-position: 0 0;
  }
  100% {
    background-position: -40px 0;
  }
}

.progress-bar
{
  transition: width 1s linear;
}

.progress-bar:after
{
  animation: progress_bar 0.8s linear infinite;
}

И самый главный момент, — это проставление width для прогресса, и числового значения в процентах. Тут все просто, логика будет следующая:

.progress progress[value="1"] + .progress-value:before 
{
  content: "1%"; 
}
.progress progress[value="1"] ~ .progress-bg .progress-bar 
{
  width: 1%; 
}

Как не сложно догадаться, таких правил нам нужно ровно 100, для этого нам и нужны препроцессоры:

Код на SCSS:

@for $i from 0 through 100 {
  .progress progress[value="#{$i}"]
  {
    & + .progress-value:before { content : '#{$i}%' }
    & ~ .progress-bg .progress-bar { width: $i * 1% }
  }
}

Код на LESS:

.generate-progress(@n, @i: 0) when (@i =< @n) {
  .progress progress[value="@{i}"]
  {
	& + .progress-value:before { content : '@{i}%' }
	& ~ .progress-bg .progress-bar { width: @i * 1% }
  }
  .generate-progress(@n, (@i + 1));
}

.generate-progress(100);

Итоговый пример.

Безусловно здесь есть большой минус — много CSS на выходе. Но по мне плюсы этого метода перекрывают огромный CSS код.

Комментарии (15)


  1. nik_the_spirit
    15.09.2015 14:05
    +3

    .progress-value:before { content: attr(value)'%'; }
    attr() возвращает значение атрибута.


  1. felicast
    15.09.2015 14:16
    +11

    У меня вкладка с анимированным прогрессбаром отъедает одно ядро процессора. Firefox 40.0.3 на Ubuntu. В хроме 45.0.2454.85 тоже. Что-то я не хотел бы чтобы такие прогрессбары были везде.


  1. GreatRash
    15.09.2015 14:17
    +3

    Интересно, почему никто не против кастомизировать нативный прогресс, но многие против кастомизации нативного селекта или скроллбара?


    1. NeonMercury
      15.09.2015 15:24
      +4

      Мне кажется, что прогрессбар предоставляет одностороннюю связь с пользователем UI, от только показывает что-то, не предлагая пользователю действий.
      С другой стороны, селекты и скроллбары предполагают двустороннюю связь и взаимодействие пользователя с ними, поэтому их интерфейс должен быть как можно более привычным.


    1. kk86
      15.09.2015 20:18

      Бытовой расизм это. Вот и все.


  1. mannaro
    15.09.2015 14:31
    +4

    при написании статьи я так и не нашел ни одной вменяемой песочницы с поддержкой LESS

    Не знаю, но у меня проблем с этим никогда не было. Самое лучшее, что видел — jsbin


  1. mannaro
    15.09.2015 14:45
    +5

    Не знаю, как автору, но мне кажется этот подход слишком уже огромным. Я всегда делал по старинке.
    И element.style.width = 66 + '%' мне кажется не намного труднее, чем element.value = 66

    Также, в указанном авторе варианте шаг равен 1%, в моем же варианте минимальный шаг не ограничен.

    Прошу у сообщества рассказать мне, в чем же нативный progress-bar лучше, чем метод «по старинке»?


    1. impulse__101
      15.09.2015 15:07

      По старинке нужно тянуть за собой JS + помимо progress.style.width = cur + '%'; нужно проставлять числовое значение progress.style.innerHTML = cur + '%'; если того требует дизайн, что менее приятно чем один раз поставить value.

      К тому же впоследствии поле value удобнее читать, нежели ширину дива.
      Если нужно отправить форму, значение value уйдет вместе с формой в отличии от style.width и так далее.
      На мой взгляд нативные значения всегда проще читать чем data атрибуты, style.width и прочее.

      Я не претендую на то что этот метод однозначно лучше, — просто перечисляю возможные плюсы.


      1. mannaro
        15.09.2015 15:13
        +4

        нужно тянуть за собой JS
        хм, так у вас же тоже нужно тянуть JS для изменения значения прогресса?
        да, с отображением процентов придется повозиться, но, можно же написать функцию, что будет делать это все сразу? Да еще и input:hidden изменять для легкого чтения.

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


      1. Mingun
        15.09.2015 18:33
        +3

        Странные у вас какие-то аргументы.

        Начнём с того, что прогресс-бар без javascript-обвязки совершенно бесполезен, поскольку для его работы в любом случае понадобится javascript (если только, конечно, нам не нужно отобразить простую статичную полоску, но с этим великолепно справляется и метод «по старинке» без лишнего оверинжинеринга и вообще в этом случае непонятно, зачем тут прогресс-бар?). Так что этот аргумент можно смело отбрасывать.

        Для того чтобы можно было «один раз поставить value» достаточно написать простейшую функцию и звать её в случае необходимости. Или заморочиться чуть больше и сделать объект-обвязку ProgressBar с методами и свойствами по вкусу.

        Гм… Скажите, а зачем вам потребовалось отправлять значение прогресс-бара вместе с формой? Прогресс-бар — это штука чисто информативная, пользователь никак не может напрямую изменить его значение, это не поле ввода, зачем его отправлять вместе с формой?

        Наконец, шаг в 1% (сколько пикселей в ширину занимает 1% на современных широкоформатных мониторах?) и чрезмерное разбухание кода ну никак нельзя отнести к плюсам данного решения.

        В общем, плюсов не увидел.


  1. impulse__101
    15.09.2015 15:22

    хм, так у вас же тоже нужно тянуть JS для изменения значения прогресса?

    Не совсем. Если рассматривать прогрессбар как отдельный виджет, то для его работы не нужен дополнительный JS код. Проставление значения все же относится к клиентскому коду, не имеющего отношения к «виджету».


    1. mannaro
      15.09.2015 15:26
      +4

      Так и у меня тоже. Достаточно проставить style="width: 30%" и все. Никакого JS.


      1. impulse__101
        15.09.2015 19:07

        Согласен. Но также нужно будет вручную задать текст в 30%. И потом еще поставить значение для input:hidden в 30%.


  1. GreatRash
    15.09.2015 18:43

    Кстати, а ведь в HTML5 есть ещё meter. Кто-нибудь знает зачем W3C дублируют функционал progress?


    1. JeStoneDev
      16.09.2015 05:25

      Из-за семантики www.w3.org/html/wg/drafts/html/master/semantics.html

      The meter element represents a scalar measurement within a known range, or a fractional value; for example disk usage, the relevance of a query result, or the fraction of a voting population to have selected a particular candidate.

      The meter element should not be used to indicate progress (as in a progress bar). For that role, HTML provides a separate progress element