Цветной прогресс бар в виде окружности на Javascript
Вступление.
Прогресс бар (progress bar) используется на сайтах, когда нужно показать, что идёт процесс загрузки информации или отобразить объём введённых пользователем данных, например, при регистрации, оформлении заказа, заполнении анкеты, при прохождении теста и т. д.
В этой статье я расскажу, как создать анимированный цветной прогресс бар в виде окружности с использованием элемента HTML5 <canvas>.
Составим техническое задание на создание прогресс бара в виде окружности:
-
Прогресс бар должен иметь возможность настройки под конкретную задачу, в настройках можно изменить:
— углы начала и окончания прогресс бара;
— радиус;
— толщину;
— палитру и количество используемых цветов;
— скорость анимации. - Возможность управления отдельными секторами прогресс бара с помощью обработчиков событий.
- Заполнение прогресс бара должно быть градиентным с использованием нескольких цветов.
- Должны отображаться проценты заполнения прогресс бара.
На этапе изучения работы анимированного прогресс бара, для его запуска будем использовать кнопку «Start».
HTML-вёрстка прогресс бара.
Как уже говорилось, прогресс бар будем создавать на основе элемента canvas. Данный элемент предназначен для создания графики при помощи JavaSript. Соответственно, HTML-вёрстка очень проста и содержит только два основных элемента: <canvas> и кнопка, запускающая рисование прогресс бара.
1 2 3 4 5 6 7 8 |
<body> <div class="wrap"> <canvas id="canvas" width="300" height="300"></canvas> <button type="button" class="button">start</button> </div> </body> |
Таблица стилей для создания прогресс бара.
Небольшая таблица стилей, которая определяет внешний вид прогресс бара и кнопки.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
html, body, div, button, canvas { margin:0; padding:0; } *, :after, :before { -webkit-box-sizing:border-box; box-sizing:border-box; } body { font:normal 16px/20px Roboto, sans-serif; color:#333; background-color:#fff; } canvas { display: block; margin: 0 auto 10px; } .wrap { width:360px; margin:0 auto; padding-top:30px; } .button { width:100px; height:30px; display:block; font-weight:500; font-size:12px; line-height:30px; font-family:Roboto, sans-serif; color:#eee; text-transform:uppercase; text-align:center; margin:0 auto; border:solid 1px #357ebd; -webkit-border-radius:3px; border-radius:3px; outline:none; -webkit-user-select:none; user-select:none; background-color:#428bca; cursor:pointer; transition:all 0.3s; } .button:hover { border-color:#285e8e; background-color:#3276b1; } .disable, .disable:hover { border:solid 1px #357ebd; background-color:#428bca; opacity:0.4; cursor:default; } |
Пишем JavaScript для создания прогресс бара.
Для ограничения области видимости нашего скрипта и исключения конфликтов с другими js-скриптами, разместим код в анонимной самозапускающейся функции.
1 2 3 4 5 6 |
;(function() { 'use strict'; })(); |
В-первую очередь, необходимо установить окружение <canvas>
(получить объект элемента и его контекст), а также инициализировать ряд глобальных, в пределах области видимости анонимной функции, объектов и переменных, которые понадобятся нам в дальнейшем. После этого запускаем настройку отрисовки цветного прогресс бара.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
var canvas = document.getElementById('canvas'), // контекст, через который будем управлять содержимым canvas ctx = canvas.getContext('2d'), // центр по горизонтали и вертикали xc = canvas.width / 2, yc = canvas.height / 2, // объект содержащий настройки options = {}, // объект кнопки, запускающей прогресс бар button = document.querySelector('.button'), i = 0; //запускаем настройку отрисовки прогресс бара init(); |
Настройка цветного прогресс бара.
Для равномерного распределения градиента по окружности, разобьём прогресс бар на семь частей (секторов). Размер одного сектора составить 45 градусов. Такое разделение, кроме равномерного градиента, позволит подключить семь обработчиков событий. Каждый обработчик будет запускать отрисовку следующего сектора. При этом, этот сектор будет начинаться с цвета, в который был окрашен конец предыдущего. За счёт этого обеспечивается плавность цветового перехода между секторами.
Нам неоднократно придётся переводить градусы в радианы. Для этого напишем маленькую функцию getRadians
:
1 2 3 4 5 6 |
function getRadians(degree) { // переводим градусы в радианы return Math.PI / 180 * degree; } |
Теперь код самой функции init
с подробными комментариями:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function init() { // длительность отрисовки одного сектора options.duration = 300, // массив со значениями цвета начала и конца градиента секторов options.colors = ['#f00', '#ff2f00', '#ff7e00', '#ffde00', '#dffc00', '#7ae000', '#2cbb00', '#15b200']; // шаг отрисовки цветов (размер сектора) в радианах options.step = getRadians(45); // получаем угол начала прогресс бара в радианах options.start = getRadians(112.5); // ширина прогресс бара в px options.width = 30, // радиус прогресс бара в px options.r = xc - options.width; // очищаем canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // запускаем рисование прогресс бара button.addEventListener('click', draw); } |
Как видно из приведённого 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
, который будем использовать для обводки сектора окружности:- Используя метод
createLinearGradient
, создаём объект линейного градиентаgradient
. В качестве аргументов метод принимает значения координат начала и конца сектора, к которому он будет применён. - Используя метод
addColorStop
определяем цвет в начале и в конце объектаgradient
. - С помощью метода
strokeStyle
назначаем полученный градиент для обводки сектора.
- Используя метод
-
4
Фиксируем время начала анимации отрисовки текущего сектора и запускаем анимацию.
Ниже представлен JS-код, реализующий данный алгоритм:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
function draw() { // получаем из массива пару цветов, которая будет использоваться // для создания градиента i-го сектора прогресс бара var startColor = options.colors[i], endColor = options.colors[i + 1]; // получаем координаты X, Y точек начала и конца i-го сектора прогресс бара var x0 = xc + Math.cos(options.start) * options.r, y0 = yc + Math.sin(options.start) * options.r, x1 = xc + Math.cos(options.start + options.step) * options.r, y1 = yc + Math.sin(options.start + options.step) * options.r; // используя метод createLinearGradient, создаём объект линейного градиента, // в качестве аргументов метод принимает значения координат начала и конца // сектора, к которому он будет применён var gradient = ctx.createLinearGradient(x0, y0, x1, y1); // используя метод addColorStop определяем цвет // в начале объекта градиента gradient.addColorStop(0, startColor); // в конце объекта градиента gradient.addColorStop(1.0, endColor); // время начала анимации отрисовки одного сектора var start = new Date().getTime(); // анимация рисования текущего сектора // данную функцию рассмотрим более подробно отдельно var fn = function() { ..... }; // старт анимации отрисовки одного сектора requestAnimationFrame(fn); } |
При создании анимации на JavaScript вместо функции
setInterval
используйте функцию requestAnimationFrame
. Эта функция позволяет синхронизировать анимацию со встроенными в браузер механизмами обновления страницы. Результатом будет более эффективное использование графического ускорителя, исключена повторная обработка одних и тех же участков страницы, меньше будет загрузка процессора и, самое главное, анимация будет более плавная, без рывков и дёрганий.
Анимация рисования одного сектора цветного прогресс бара.
Давайте теперь рассмотрим принцип линейной программной анимации применительно к нашей задаче. Для этого необходимо определить ряд параметров, с которыми будем проводить вычисления:
- option.duration
- Время анимации в ms, задано в настройках;
- option.step
- Размер сектора в rad, задан в настройках;
- now
- Время прошедшее с начала анимации, представляет собою разность между текущим временем и временем начала (старта) анимации
start
.
Исходя из полученных параметров, можно рассчитать, на какой угол будет отрисован (закрашен) сектор в текущей итерации:
1 2 3 |
var inc = options.step * now / options.duration; |
Теперь можно перейти непосредственно к закрашиванию сектора. Для этого используем функцию drawSector
, которая закрасит часть сектора на угол равный inc
. При вызове функции, ей передаются следующие параметры:
— цвет, используемый для отрисовки части текущего сектора;
— толщина прогресс бара;
— угол, на который будет отрисован текущий сектор.
Прежде, чем начать писать JS-код функции fn
, необходимо разобраться, как происходит поэтапное закрашивание сектора:
Как видно из рисунка, при каждой новой итерации сектор закрашивается каждый раз с начала, но на больший угол, при этом слои с закрашиваемыми участками при каждой итерации накладываются друг на друга. Из-за многократного наложения слоёв края прогресс бара получаются рваными. Что бы избежать этого, необходимо градиент, отрисованный в предыдущей итерации, закрасить цветом фона, а уже после этого наложить градиент. Для этого вызовем функцию drawSector
дважды: при первом вызове первым параметром будет цвет фона, а при втором вызове — gradient
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var fn = function() { // время прошедшее от начала отрисовки сектора var now = new Date().getTime() - start; // если текущее время превысило время анимации, присваиваем ему значение // времени анимации, в противном случае, сектор может получиться // большего размера, чем планировалось now = (now < options.duration) ? now : options.duration; // на сколько радиан должен быть отрисован текущий сектор var inc = options.step * now / options.duration; // предварительно закрашиваем текущий сектор белым цветом на угол равный inc // толщину берём на 2px больше, чтобы закрасить возможные артефакты drawSector('#fff', options.width + 2, inc); // закрашиваем текущий сектор градиентом на угол равный inc drawSector(gradient, options.width, inc); }; |
Сравниваем время, прошедшее с начала анимации (now
), с временем, отведённым на анимацию (option.duration
).
Если now < option.duration
, то продолжаем закрашивать текущий сектор, рекурсивно вызвав функцию fn
с помощью метода requestAnimationFrame
.
В противном случае будем считать, что текущий сектор полностью закрашен градиентом и переходим к отрисовке следующего сектора. Для этого увеличиваем индекс i
на единицу, чтобы выбрать из массива цветов следующую пару, а угол, с которого начинает отрисовываться следующий сектор увеличим на options.step
и снова вызовем функцию draw
.
Давай представим это в виде JS-кода и добавим этот код в конец функции fn
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
var fn = function() { // время прошедшее от начала отрисовки сектора var now = new Date().getTime() - start; // если текущее время превысило время анимации, присваиваем ему значение // времени анимации, в противном случае, сектор может получиться // большего размера, чем планировалось now = (now < options.duration) ? now : options.duration; // на сколько радиан должен быть отрисован текущий сектор var inc = options.step * now / options.duration; // предварительно закрашиваем текущий сектор белым цветом на угол равный inc // толщину берём на 2px больше, чтобы закрасить возможные артефакты drawSector('#fff', options.width + 2, inc); // закрашиваем текущий сектор градиентом на угол равный inc drawSector(gradient, options.width, inc); // если текущее время меньше времени анимации, продолжаем // рисование текущего сектора if (now < options.duration) { requestAnimationFrame(fn); } else { // увеличиваем индекс на единицу, чтобы выбрать из массива цветов следующую пару i++; // угол, с которого начинает отрисовываться следующий сектор options.start += options.step; // запускаем рисование следующего сектора, рекурсивно // вызывая функцию draw return draw(); } }; |
А как быть, если текущий сектор был последним? В этом случае необходимо прервать работу функции draw
.
Для решения этой проблемы лучше всего подойдёт индекс i
. Можно сравнивать его значение с количеством секторов, которое должно быть отрисовано или с длинной массива, содержащего набор цветов для закрашивания прогресс бара. Я решил остановиться на втором варианте.
Добавим в функцию fn
следующий JS-код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
var fn = function() { // время прошедшее от начала отрисовки сектора var now = new Date().getTime() - start; // если текущее время превысило время анимации, присваиваем ему значение // времени анимации, в противном случае, сектор может получиться // большего размера, чем планировалось now = (now < options.duration) ? now : options.duration; // на сколько радиан должен быть отрисован текущий сектор var inc = options.step * now / options.duration; // предварительно закрашиваем текущий сектор белым цветом на угол равный inc // толщину берём на 2px больше, чтобы закрасить возможные артефакты drawSector('#fff', options.width + 2, inc); // закрашиваем текущий сектор градиентом на угол равный inc drawSector(gradient, options.width, inc); // если текущее время меньше времени анимации, продолжаем // рисование текущего сектора if (now < options.duration) { requestAnimationFrame(fn); } else { // увеличиваем индекс на единицу, чтобы выбрать из массива цветов следующую пару i++; // все сектора отрисованы, заканчиваем работу функции if (i >= options.colors.length - 1) { // делаем кнопку запуска прогресс бара неактивно button.classList.add('disable'); // удаляем зарегистрированный обработчик события button.removeEventListener('click', draw); // выходим из функции рисования прогресс бара return; } // угол, с которого начинает отрисовываться следующий сектор options.start += options.step; // запускаем рисование следующего сектора, рекурсивно // вызывая функцию draw return draw(); } }; |
Теперь полный код функции draw
выглядит так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
function draw() { // получаем из массива пару цветов, которая будет использоваться // для создания градиента i-го сектора прогресс бара var startColor = options.colors[i], endColor = options.colors[i + 1]; // получаем координаты X, Y точек начала и конца i-го сектора прогресс бара var x0 = xc + Math.cos(options.start) * options.r, y0 = yc + Math.sin(options.start) * options.r, x1 = xc + Math.cos(options.start + options.step) * options.r, y1 = yc + Math.sin(options.start + options.step) * options.r; // используя метод createLinearGradient, создаём объект линейного градиента, // в качестве аргументов метод принимает значения координат начала и конца // сектора, к которому он будет применён var gradient = ctx.createLinearGradient(x0, y0, x1, y1); // используя метод addColorStop определяем цвет // в начале объекта градиента gradient.addColorStop(0, startColor); // в конце объекта градиента gradient.addColorStop(1.0, endColor); // старт анимации отрисовки одного сектора var start = new Date().getTime(); var fn = function() { // время прошедшее от начала отрисовки сектора var now = new Date().getTime() - start; // если текущее время превысило время анимации, присваиваем ему значение // времени анимации, в противном случае, сектор может получиться // большего размера, чем планировалось now = (now < options.duration) ? now : options.duration; // на сколько должен быть отрисован текущий сектор var inc = options.step * now / options.duration; // предварительно закрашиваем текущий сектор белым цветом на угол равный inc // толщину берём на 2px больше, чтобы закрасить возможные артефакты drawSector('#fff', options.width + 2, inc); // закрашиваем текущий сектор градиентом на угол равный inc drawSector(gradient, options.width, inc); // если текущее время меньше времени анимации, продолжаем // рисование текущего сектора if (now < options.duration) { requestAnimationFrame(fn); } else { // увеличиваем индекс на единицу, чтобы выбрать из массива цветов следующую пару i++; // все сектора отрисованы, заканчиваем работу функции if (i >= options.colors.length - 1) { // делаем кнопку запуска прогресс бара неактивно button.classList.add('disable'); // удаляем зарегистрированный обработчик события button.removeEventListener('click', draw); // выходим из функции рисования прогресс бара return; } // угол, с которого начинает отрисовываться следующий сектор options.start += options.step; // запускаем рисование следующего сектора, рекурсивно // вызывая функцию draw return draw(); } }; // старт анимации отрисовки одного сектора requestAnimationFrame(fn); } |
Осталось рассмотреть JS-код функции drawSector
. Код очень простой для понимания и достаточно будет комментариев внутри самой функции:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function drawSector(colorFill, widthWheel, inc) { // beginPath используется чтобы начать серию действий, описывающих отрисовку фигуры. // каждый новый вызов этого метода сбрасывает все действия предыдущего и начинает // рисовать заново ctx.beginPath(); // устанавливаем цвет или стиль, используемый при выполнении обводки ctx.strokeStyle = colorFill; // устанавливаем ширину линии, которой будет рисоваться дуга ctx.lineWidth = widthWheel; // создаём дугу, где xc и yc центр окружности, далее радиус, начальный и конечный угол ctx.arc(xc, yc, options.r, options.start, options.start + inc); // рисуем дугу (часть сектора), с параметрами заданными ранее с помощью // strokeStyle, lineWidth и arc ctx.stroke(); return; } |
Подложка под цветной прогресс бар в виде окружности.
Итак, прогресс бар нарисован с учётом всех заданных нами настроек. Но... Посмотрите, как выглядит часть страницы, до того, как отрисован прогресс бар - достаточно большое пустое место над кнопкой запуска.
Давайте, на месте будущего прогресс бара нарисуем подложку, поверх которой он и будет расположен.
Для рисования подложки будем использовать ту же функцию, что и для рисования сектора - drawSector
, вызвав её из функции init
. Анимацию при создании подложки использовать не будем.
Обновлённый JS-код функции init
с дополнением:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function init() { // длительность отрисовки одного сектора options.duration = 300, // массив со значениями цвета начала и конца градиента секторов options.colors = ['#f00', '#ff2f00', '#ff7e00', '#ffde00', '#dffc00', '#7ae000', '#2cbb00', '#15b200']; // шаг отрисовки цветов (размер сектора) в радианах options.step = getRadians(45); // получаем угол начала прогресс бара в радианах options.start = getRadians(112.5); // ширина прогресс бара в px options.width = 30, // радиус прогресс бара в px options.r = xc - options.width; // очищаем canvas ctx.clearRect(0, 0, canvas.width, canvas.height); // рисуем подложку без анимации drawSector('#eee', options.width); // запускаем рисование прогресс бара button.addEventListener('click', draw); } |
В функцию drawSector
так же внесём дополнение - вычисление угла окончания отрисовки в зависимости от параметра inc
.
Обновлённый JS-код функции drawSector
с дополнением:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function drawSector(colorFill, widthWheel, inc) { // beginPath используется чтобы начать серию действий, описывающих отрисовку фигуры. // каждый новый вызов этого метода сбрасывает все действия предыдущего и начинает // рисовать заново ctx.beginPath(); // устанавливаем цвет или стиль, используемый при выполнении обводки ctx.strokeStyle = colorFill; // устанавливается ширина линии, которой будет рисоваться дуга ctx.lineWidth = widthWheel; // вычисляем конечный угол, если inc не задан, значит рисуется подложка // и задаётся конечный угол прогресс бара var end = (inc === undefined) ? getRadians(427.5) : options.start + inc; // создаётся дуга, где xc и yc центр окружности, далее радиус, начальный и конечный угол ctx.arc(xc, yc, options.r, options.start, end); // рисуется дуга (часть сектора), с параметрами заданными с помощью // strokeStyle, lineWidth и arc ctx.stroke(); return; } |
Вывод заполнения цветного прогресс бара в процентах
Для улучшения визуализации, давайте добавим отображение заполнения прогресс бара в процентах. Для этого создадим функцию showPercents
, которая будет вызываться из функции fn
, отвечающей за анимацию отрисовки прогресс бара.
Параметрами функции будут: номер текущего сектора i
и угол отрисовки текущего сектора inc
.
В функцию fn
, после строк:
1 2 3 4 5 6 7 |
// предварительно закрашиваем текущий сектор белым цветом на угол равный inc // толщину берём на 2px больше, чтобы закрасить возможные артефакты drawSector('#fff', options.width + 2, inc); // закрашиваем текущий сектор градиентом на угол равный inc drawSector(gradient, options.width, inc); |
добавим вызов функции showPercents
:
1 2 3 4 |
// выводим проценты заполнения прогресс бара showPercents(i, inc); |
Сам JS-код функции showPercents
несложный, для его понимания достаточно будет приведённых в нём комментариев:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
function showPercents(i, inc) { // угол в радианах, на который отрисован прогресс бар // на текущий момент var angle = options.step * i + inc, // получаем проценты, где 0.0549779 результат деления // options.step * 7 на 100 percents = Math.ceil(angle / 0.0549779); // цвет текста ctx.fillStyle = '#666'; // параметры шрифта и текста ctx.font = '400 20px Roboto'; // центрирование текста по горизонтали ctx.textAlign = 'center'; // центрирование текста по вертикали ctx.textBaseline = 'center'; // очищаем область canvas в которую будет выведен текст // область представлена в виде прямоугольника заданного // начальной точкой (120px,125px), шириной и высотой (60px,30px) // отсчёт координат идёт от верхнего левого угла canvas ctx.clearRect(120, 125, 60, 30); // выводим текст в центр canvas ctx.fillText(percents + '%', xc, yc); } |
Итак, посмотрим на результат работы нашего скрипта:
Как видно на скриншоте, градиент не является сплошным, видны не закрашенные до конца стыки секторов. Скорее всего, этот баг возникает при переводе градусов в радианы из-за округления результата вычислений. Давайте попробуем устранить этот баг.
Границы секторов цветного прогресс бара в виде окружности.
Чтобы устранить выявленный недостаток, необходимо дополнительно закрасить стыки секторов, нарисовав линию поверх границы сектора. Возможны два варианта прорисовки границ секторов:
- закрасить стыки секторов цветом, контрастным к цветовой палитре прогресс бара;
- использовать для закраски стыка секторов цвет текущего градиента.
JS-код для обоих вариантов практически идентичен. Отличие заключается лишь в том, что в первом случае цвет границы мы выбираем сами. Во втором случае, цвет получаем из массива options.colors
в зависимости от значения переменной i
, являющейся номером текущего закрашиваемого сектора.
JS-код, рисующей границу секторов, реализован в функции drawLine
, которая принимает один параметр - номер текущего сектора.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
function drawLine(i) { // определяем координаты начала и конца линии границы текущего сектора var x0 = xc + Math.cos(options.start) * (options.r + 15), y0 = yc + Math.sin(options.start) * (options.r + 15), x1 = xc + Math.cos(options.start) * (options.r - 15), y1 = yc + Math.sin(options.start) * (options.r - 15); ctx.beginPath(); // Вариант 1 - назначаем цвет границы стыка всех секторов //ctx.strokeStyle = '#fff'; // Вариант 2 - выбираем цвет стыка текущего и следующего секторов из массива ctx.strokeStyle = options.colors[i]; // устанавливаем координаты начала и конца рисуемой линии и // её толщину ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.lineWidth = 1; // рисуем границу секторов ctx.stroke(); return; } |
Как видно из кода, всё отличие состоит от значения, задаваемого свойству ctx.strokeStyle
, отвечающего за цвет рисуемой линии.
Вызывается функция drawLine
из функции fn
1 2 3 4 5 6 7 8 9 10 11 |
// предварительно закрашиваем текущий сектор белым цветом на угол равный inc // толщину берём на 2px больше, чтобы закрасить возможные артефакты drawSector('#fff', options.width + 2, inc); // закрашиваем текущий сектор градиентом на угол равный inc drawSector(gradient, options.width, inc); // закрашиваем стыки секторов drawLine(i); // выводим проценты заполнения прогресс бара showPercents(i, inc); |
Использование цветного прогресс бара для нескольких событий.
Рассмотренный нами JS-код, рисует цветной прогресс бар полностью после клика на кнопку "Srart". А как быть, если мы хотим, чтобы прогресс бар отображал этапы прохождения теста, полноту заполнения анкеты и т. д.? В этом нет ничего сложного, достаточно внести в уже существующий код пару изменений и дополнений:
-
Из функции
draw
необходимо убрать рекурсивный вызов этой функции, запускающий рисование следующего сектора:123return draw(); -
В конец функции
init
добавить дополнительные регистраторы обработчиков событий, которые будут вызывать функциюdraw
, для рисования следующего сектора цветного прогресс бара.123456789// запускаем рисование первого сектора прогресс бараbutton1.addEventListener('click', draw);// запускаем рисование второго сектора прогресс бараbutton2.addEventListener('click', draw);...// запускаем рисование N-го сектора прогресс бараbuttonN.addEventListener('click', draw);
Заключение.
У читателей, хорошо знакомых с canvas, может возникнуть вопрос, зачем использовать сектора, если нужен прогресс бар для одного события. Используя метод addColorStop
можно задать цвет градиента в различных точках прогресс бара, например:
1 2 3 4 5 6 7 8 |
gradient.addColorStop(0, color1); gradient.addColorStop(0.2, color2); gradient.addColorStop(0.4, color3); gradient.addColorStop(0.6, color4); gradient.addColorStop(0.8, color5); gradient.addColorStop(1, color6); |
К сожалению, для прогресс бара в виде окружности этот способ не подходит, т. к. градиент распространяется прямолинейно, слева направо, а не по длине окружности. Полученный результат будет далёк от ожидаемого. Поэтому, лучше разделить окружность на сектора. При этом, чем больше количество секторов (в пределах разумного), тем равномернее градиент.
Комментарии
-
Комментарии должны содержать вопросы и дополнения по статье, ответы на вопросы других пользователей.
Комментарии содержащие обсуждение политики, будут безжалостно удаляться. -
Для удобства чтения Вашего кода, не забываейте его форматировать. Вы его можете подсветить код с помощью тега
<pre>
:
—<pre class="lang:xhtml">
- HTML;
—<pre class="lang:css">
- CSS;
—<pre class="lang:javascript">
- JavaScript. - Если что-то не понятно в статье, постарайтесь указать более конкретно, что именно не понятно.
-
-
Появилась одна мысль, как решить проблему. Если уменьшать угол, закрашиваемый за один клик, то цвета заканчиваются до того, как заполнится окружность. А что если уменьшать угол (при этом увеличивая количество секций) и одновременно добавлять в массив цветов еще цвета, чтобы их количество совпадало с новым количеством сегментов? А что если добавить в массив цветов 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%)’;
}
Теперь другая проблема. После установки некоторого значения показаний прогрессбара , не могу обнулить эти показания без перезагрузки страницы, а нужно обнулять именно без перезагрузки. Несколько часов уже бьюсь над этим
-
Спасибо, классно получилось. У меня вопрос. Для использования без кнопки старт, вы пишете, что нужно убрать рекурсивный вызов draw и повесить обработчики на каждый сектор. В таком случае, по очереди закрашивается каждый следующий сектор, а отображаемые проценты соответственно равны 15, 29, 43, 58, 72, 86 и 100. То есть они фиксированы. А что если мне нужно закрасить часть сектора, и отобразить, например 11%? Или закрасить 2,5 сектора и отобразить 35%?. Вот мне сейчас нужно отобразить процент просмотревших видеолекцию студентов, как это сделать? То есть как заставить прогрессбар отображать конкретно то значение, которое я ему передам? Пытаюсь разобраться сам, но пока не получилось. В js недавно.