Цветной прогресс бар в виде окружности на Javascript

Вступление.

Прогресс бар (progress bar) используется на сайтах, когда нужно показать, что идёт процесс загрузки информации или отобразить объём введённых пользователем данных, например, при регистрации, оформлении заказа, заполнении анкеты, при прохождении теста и т. д.
В этой статье я расскажу, как создать анимированный цветной прогресс бар в виде окружности с использованием элемента HTML5 <canvas>.

Цветной прогресс бар в виде окружности

Составим техническое задание на создание прогресс бара в виде окружности:

  1. Прогресс бар должен иметь возможность настройки под конкретную задачу, в настройках можно изменить:
    — углы начала и окончания прогресс бара;
    — радиус;
    — толщину;
    — палитру и количество используемых цветов;
    — скорость анимации.
  2. Возможность управления отдельными секторами прогресс бара с помощью обработчиков событий.
  3. Заполнение прогресс бара должно быть градиентным с использованием нескольких цветов.
  4. Должны отображаться проценты заполнения прогресс бара.

На этапе изучения работы анимированного прогресс бара, для его запуска будем использовать кнопку «Start».

HTML-вёрстка прогресс бара.

Как уже говорилось, прогресс бар будем создавать на основе элемента canvas. Данный элемент предназначен для создания графики при помощи JavaSript. Соответственно, HTML-вёрстка очень проста и содержит только два основных элемента: <canvas> и кнопка, запускающая рисование прогресс бара.

Таблица стилей для создания прогресс бара.

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

Пишем JavaScript для создания прогресс бара.

Для ограничения области видимости нашего скрипта и исключения конфликтов с другими js-скриптами, разместим код в анонимной самозапускающейся функции.

В-первую очередь, необходимо установить окружение <canvas> (получить объект элемента и его контекст), а также инициализировать ряд глобальных, в пределах области видимости анонимной функции, объектов и переменных, которые понадобятся нам в дальнейшем. После этого запускаем настройку отрисовки цветного прогресс бара.

Настройка цветного прогресс бара.

Для равномерного распределения градиента по окружности, разобьём прогресс бар на семь частей (секторов). Размер одного сектора составить 45 градусов. Такое разделение, кроме равномерного градиента, позволит подключить семь обработчиков событий. Каждый обработчик будет запускать отрисовку следующего сектора. При этом, этот сектор будет начинаться с цвета, в который был окрашен конец предыдущего. За счёт этого обеспечивается плавность цветового перехода между секторами.

Нам неоднократно придётся переводить градусы в радианы. Для этого напишем маленькую функцию getRadians:

Теперь код самой функции init с подробными комментариями:

Как видно из приведённого JS-кода, при клике на кнопку «start», запускается рисование прогресс бара.
Прежде чем начнём писать код этой функции, давайте рассмотрим подробный алгоритм рисования.

Алгоритм рисования цветного прогресс бара.

  • 1

    Получаем из массива options.colors, содержащего палитру цветов прогресс бара, пару, которая будет использоваться в качестве начала и конца градиента текущего сектора.

  • 2

    Получаем координаты X, Y точек начала и конца текущего сектора прогресс бара. Это можно сделать при помощи следующих формул:
    x = xc + r * cos(a)
    y = yc + r * sin(a)

    где xc и yc — координаты центра окружности, r — радиус, a — угол.

    Хочу напомнить, что за начало отсчёта принимается верхний левый угол элемента <canvas>.

    Координаты точки на окружности
  • 3

    Создаём объект gradient, который будем использовать для обводки сектора окружности:

    1. Используя метод createLinearGradient, создаём объект линейного градиента gradient. В качестве аргументов метод принимает значения координат начала и конца сектора, к которому он будет применён.
    2. Используя метод addColorStop определяем цвет в начале и в конце объекта gradient.
    3. С помощью метода strokeStyle назначаем полученный градиент для обводки сектора.
  • 4

    Фиксируем время начала анимации отрисовки текущего сектора и запускаем анимацию.

Ниже представлен JS-код, реализующий данный алгоритм:

Запомните, это очень важно.
При создании анимации на JavaScript вместо функции setInterval используйте функцию requestAnimationFrame. Эта функция позволяет синхронизировать анимацию со встроенными в браузер механизмами обновления страницы. Результатом будет более эффективное использование графического ускорителя, исключена повторная обработка одних и тех же участков страницы, меньше будет загрузка процессора и, самое главное, анимация будет более плавная, без рывков и дёрганий.

Анимация рисования одного сектора цветного прогресс бара.

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

option.duration
Время анимации в ms, задано в настройках;
option.step
Размер сектора в rad, задан в настройках;
now
Время прошедшее с начала анимации, представляет собою разность между текущим временем и временем начала (старта) анимации start.

Исходя из полученных параметров, можно рассчитать, на какой угол будет отрисован (закрашен) сектор в текущей итерации:

Теперь можно перейти непосредственно к закрашиванию сектора. Для этого используем функцию drawSector, которая закрасит часть сектора на угол равный inc. При вызове функции, ей передаются следующие параметры:
— цвет, используемый для отрисовки части текущего сектора;
— толщина прогресс бара;
— угол, на который будет отрисован текущий сектор.

Прежде, чем начать писать JS-код функции fn, необходимо разобраться, как происходит поэтапное закрашивание сектора:

Закрашивание сектора цветного прогресс бара

Как видно из рисунка, при каждой новой итерации сектор закрашивается каждый раз с начала, но на больший угол, при этом слои с закрашиваемыми участками при каждой итерации накладываются друг на друга. Из-за многократного наложения слоёв края прогресс бара получаются рваными. Что бы избежать этого, необходимо градиент, отрисованный в предыдущей итерации, закрасить цветом фона, а уже после этого наложить градиент. Для этого вызовем функцию drawSector дважды: при первом вызове первым параметром будет цвет фона, а при втором вызове — gradient.

Сравниваем время, прошедшее с начала анимации (now), с временем, отведённым на анимацию (option.duration).
Если now < option.duration, то продолжаем закрашивать текущий сектор, рекурсивно вызвав функцию fn с помощью метода requestAnimationFrame.
В противном случае будем считать, что текущий сектор полностью закрашен градиентом и переходим к отрисовке следующего сектора. Для этого увеличиваем индекс i на единицу, чтобы выбрать из массива цветов следующую пару, а угол, с которого начинает отрисовываться следующий сектор увеличим на options.step и снова вызовем функцию draw.

Давай представим это в виде JS-кода и добавим этот код в конец функции fn:

А как быть, если текущий сектор был последним? В этом случае необходимо прервать работу функции draw.
Для решения этой проблемы лучше всего подойдёт индекс i. Можно сравнивать его значение с количеством секторов, которое должно быть отрисовано или с длинной массива, содержащего набор цветов для закрашивания прогресс бара. Я решил остановиться на втором варианте.

Добавим в функцию fn следующий JS-код:

Теперь полный код функции draw выглядит так:

Осталось рассмотреть JS-код функции drawSector. Код очень простой для понимания и достаточно будет комментариев внутри самой функции:

Подложка под цветной прогресс бар в виде окружности.

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

Подложка под цветной прогресс бар в виде окружности

Для рисования подложки будем использовать ту же функцию, что и для рисования сектора - drawSector, вызвав её из функции init. Анимацию при создании подложки использовать не будем.
Обновлённый JS-код функции init с дополнением:

В функцию drawSector так же внесём дополнение - вычисление угла окончания отрисовки в зависимости от параметра inc.
Обновлённый JS-код функции drawSector с дополнением:

Вывод заполнения цветного прогресс бара в процентах

Для улучшения визуализации, давайте добавим отображение заполнения прогресс бара в процентах. Для этого создадим функцию showPercents, которая будет вызываться из функции fn, отвечающей за анимацию отрисовки прогресс бара.
Параметрами функции будут: номер текущего сектора i и угол отрисовки текущего сектора inc.

В функцию fn, после строк:

добавим вызов функции showPercents:

Сам JS-код функции showPercents несложный, для его понимания достаточно будет приведённых в нём комментариев:

Итак, посмотрим на результат работы нашего скрипта:

Цветной прогресс бар в виде окружности

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

Границы секторов цветного прогресс бара в виде окружности.

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

  1. закрасить стыки секторов цветом, контрастным к цветовой палитре прогресс бара;
  2. использовать для закраски стыка секторов цвет текущего градиента.
Цветной прогресс бар в виде окружности

JS-код для обоих вариантов практически идентичен. Отличие заключается лишь в том, что в первом случае цвет границы мы выбираем сами. Во втором случае, цвет получаем из массива options.colors в зависимости от значения переменной i, являющейся номером текущего закрашиваемого сектора.

JS-код, рисующей границу секторов, реализован в функции drawLine, которая принимает один параметр - номер текущего сектора.

Как видно из кода, всё отличие состоит от значения, задаваемого свойству ctx.strokeStyle, отвечающего за цвет рисуемой линии.

Вызывается функция drawLine из функции fn

Использование цветного прогресс бара для нескольких событий.

Рассмотренный нами JS-код, рисует цветной прогресс бар полностью после клика на кнопку "Srart". А как быть, если мы хотим, чтобы прогресс бар отображал этапы прохождения теста, полноту заполнения анкеты и т. д.? В этом нет ничего сложного, достаточно внести в уже существующий код пару изменений и дополнений:

  1. Из функции draw необходимо убрать рекурсивный вызов этой функции, запускающий рисование следующего сектора:
  2. В конец функции init добавить дополнительные регистраторы обработчиков событий, которые будут вызывать функцию draw, для рисования следующего сектора цветного прогресс бара. где button1, button2, ..., buttonN - элементы DOM, по которым нужно сделать клик, для отрисовки очередного сектора прогресс бара.

Заключение.

У читателей, хорошо знакомых с canvas, может возникнуть вопрос, зачем использовать сектора, если нужен прогресс бар для одного события. Используя метод addColorStop можно задать цвет градиента в различных точках прогресс бара, например:

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

Комментарии

Всего: 3 комментария
Требования при посте комментариев:
  1. Комментарии должны содержать вопросы и дополнения по статье, ответы на вопросы других пользователей.
    Комментарии содержащие обсуждение политики, будут безжалостно удаляться.
  2. Для удобства чтения Вашего кода, не забываейте его форматировать. Вы его можете подсветить код с помощью тега <pre>:
    <pre class="lang:xhtml"> - HTML;
    <pre class="lang:css"> - CSS;
    <pre class="lang:javascript"> - JavaScript.
  3. Если что-то не понятно в статье, постарайтесь указать более конкретно, что именно не понятно.
  • Спасибо, классно получилось. У меня вопрос. Для использования без кнопки старт, вы пишете, что нужно убрать рекурсивный вызов draw и повесить обработчики на каждый сектор. В таком случае, по очереди закрашивается каждый следующий сектор, а отображаемые проценты соответственно равны 15, 29, 43, 58, 72, 86 и 100. То есть они фиксированы. А что если мне нужно закрасить часть сектора, и отобразить, например 11%? Или закрасить 2,5 сектора и отобразить 35%?. Вот мне сейчас нужно отобразить процент просмотревших видеолекцию студентов, как это сделать? То есть как заставить прогрессбар отображать конкретно то значение, которое я ему передам? Пытаюсь разобраться сам, но пока не получилось. В js недавно.

    • Появилась одна мысль, как решить проблему. Если уменьшать угол, закрашиваемый за один клик, то цвета заканчиваются до того, как заполнится окружность. А что если уменьшать угол (при этом увеличивая количество секций) и одновременно добавлять в массив цветов еще цвета, чтобы их количество совпадало с новым количеством сегментов? А что если добавить в массив цветов 100 оттенков от красного до зеленого, и уменьшить закрашиваемый угол настолько, чтобы получилось 100 секций? В таком случае, каждый вызов draw будет закрашивать окружность на 1/100 часть, и соответственно проценты будут увеличиваться на 1.

    • В общем, с вышеозвученной проблемой разобрался, как и написал, сделал 100 секций, правда делать массив с 100 оттенками вручную было бы глупостью, поэтому решил эту проблему вот такой функцией
      function percentageToHsl(percentage, hue0, hue1) {
      var hue = ((percentage/100) * (hue1 — hue0)) + hue0;
      return ‘hsl(‘ + hue + ‘, 100%, 50%)’;
      }
      Теперь другая проблема. После установки некоторого значения показаний прогрессбара , не могу обнулить эти показания без перезагрузки страницы, а нужно обнулять именно без перезагрузки. Несколько часов уже бьюсь над этим

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *