Выполняю, подождите...
 
 
Библиотека

Круговой прогресс бар на HTML и CSS

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

Когда я искал способы реализации кругового прогресс бара в Интернете, мне часто попадались решения на основе Canvas и SVG, либо нечто громоздкое с использованием JavaScript. Тогда я понял, что лучше придумать свой способ, тем более, что нынешние технологии позволяют сделать это весьма просто и кратко, то есть, не используя много кода.

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

Стоит также отметить, что использование HTML и CSS позволяет применять различные стилистические приемы, сохраняя гибкость элемента и компактность, что может оказаться менее значительным относительно SVG или Canvas, например, при использовании растровых изображений для фонов, множественных теней, анимационных эффектов и прочего.

Итак, для начала вот весь код кругового прогресс бара на HTML и CSS. Затем, если вам понадобится справка, ниже я объясняю как это работает.

Исходный код круглого прогресс бара

<div class="diagram progress" data-percent="18">
    <div class="piece left"></div>
    <div class="piece right"></div>
    <div class="text">
        <div>
            <b>18</b>
            <span>PERCENT</span>
        </div>
    </div>
</div>
.diagram {
    width: 250px;
    height:250px;
    border-radius: 50%;
    background: #990;
    position: relative;
    overflow: hidden;
}
.diagram::before {
    content: '';
    display: block;
    position: absolute;
    top:20px;
    left:20px;
    right:20px;
    bottom:20px;
    border-radius: 50%;
    background: #ccc;
    z-index: 3;
    opacity: 1;
}
.diagram .piece {
    width: 100%;
    height: 100%;
    left: 0;
    right: 0;
    overflow: hidden;
    position: absolute;
}
.diagram .piece::before {
    content: '';
    display: block;
    position: absolute;
    width: 50%;
    height: 100%;
}
.diagram .piece.left {
    transform: rotate(0deg);
    z-index: 2;
    border-radius: 50%; /* only FireFox < 75.0v (fix bug)*/
}
.diagram.over_50 .piece.left {
    transform: rotate(180deg);
}
.diagram .piece.right {
    transform: rotate(180deg);
    z-index: 1;
    border-radius: 50%; /* only FireFox < 75.0v (fix bug)*/
}
.diagram.over_50 .piece.right {
    transform: rotate(360deg);
}
.diagram .left::before {
    background: #059;
}
.diagram.over_50 .left::before {
    background: #990;
}
.diagram .right::before {
    background: #059;
}
.diagram .text {
    position: absolute;
    z-index: 3;
    top: 0;
    bottom: 0;
    left:0;
    right:0;
    text-align: center;
    display: flex;
    align-items: center;
    justify-content: center;
}
.diagram .text b {
    font-size: 72px;
}
.diagram .text span {
    font-size: 16px;
    display: block;
}

Как я уже говорил, для статического отображения прогресс бара не требуется JavaScript и для этого достаточно вручную прописать значения CSS-свойства transform: rotate(); для следующих элементов:

<div class="piece left"></div> — блок перекрывающий правую или левую половины в зависимости от значения.
<div class="piece right"></div> — блок отображающий само значение с помощью своего угла поворота, который рассчитывается по формуле (360 * текущее значение / 100) + 180, где 100 — это максимальное значение прогресс бара, которое может принимать любое другое значение.

Но если вы хотите немного автоматизировать процесс расчета значения визуальных показаний (особенно это полезно, когда прогресс баров может быть несколько), можно прибегнуть к помощи JavaScript, передавая ему значение через атрибут data-percent и рассчитывая значения CSS свойств transform следующим методом:

function progressView(){
    let diagramBox = document.querySelectorAll('.diagram.progress');
    diagramBox.forEach((box) => {
        let deg = (360 * box.dataset.percent / 100) + 180;
        if(box.dataset.percent >= 50){
            box.classList.add('over_50');
        }else{
            box.classList.remove('over_50');
        }
        box.querySelector('.piece.right').style.transform = 'rotate('+deg+'deg)';
    });
}
progressView();

Как работает прогресс бар

Принцип отображения реализован следующим нехитрым способом.
В основном блоке (div) с зеленым цветом фона, который визуализирует настоящее значение, заложены два дочерний блока.

Один дочерний блок, который я пепрекрасил на анимации ниже для наглядности в коричневый цвет, закрывает попеременно только половины родительского блока с зелёным фоном и при изменении своего положения принимает цвет большей (занятой синей или наоборот незанятой зеленой) области диаграммы. Этот (коричневый) блок является вспомогательным, поскольку второй дочерний блок (синий), который вращаясь визуально указывает точное значение, способен перекрыть только половину родительского блока. Таким образом, применив к основному блоку свойства overflow:hidden; и border-radius:50%; вращением этих внутренних блоков, мы создаем иллюзию постепенного замещения в нашем «бублике» одного цвета другим цветом.

Принцип работы кругового прогресс бара на HTML

Анимация блоков кругового прогресс бара отражающая принцип его работы

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

let diagramBox = document.querySelectorAll('.diagram.progress');

В этой строке мы выберем все элементы кругового прогресс бара с классами .diagram.progress, поскольку, будем считать, что их может быть несколько.

diagramBox.forEach((box) => {...});

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

let deg = (360 * box.dataset.percent / 100) + 180;

В этой строке мы считаем, какой угол должен принять правый блок (плавно вращающийся) в зависимости от полученного значения из атрибута data-percent. В принципе, значения можно брать прямо из тега <b>, но мне просто удобней использовать атрибуты data-.

if(box.dataset.percent >= 50){
    box.classList.add('over_50');
}else{
    box.classList.remove('over_50');
}

Следующим условием мы определяем, когда значение больше половины, то левому блоку (перескакивающему с одной половины на другую) добавляем класс .over_50, для которого прописаны свойства разворота на 180 градусов и перекрашивания фона в противоположный цвет (cм. CSS). А когда значения ниже 50%, просто удаляем этот класс.

box.querySelector('.piece.right').style.transform = 'rotate('+deg+'deg)';

Теперь нам осталось указать правому блоку градус поворота при помощи изменения CSS-свойства transform. Вот и вся «магия».

Пример: таймер обратного отсчета на JavaScript

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

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

<div class="diagram timer" data-seconds="60">
    <div class="piece left"></div>
    <div class="piece right"></div>
    <div class="text">
        <div>
            <b>100</b>
            <span>SECONDS</span>
        </div>
    </div>
</div> 
function timer(seconds){
    let diagramBox = document.querySelector('.diagram.timer');
    seconds = seconds || diagramBox.dataset.seconds;

    let deg = (360 * seconds / diagramBox.dataset.seconds) + 180;
    if(seconds >= diagramBox.dataset.seconds / 2){
        diagramBox.classList.add('over_50');
    }else{
        diagramBox.classList.remove('over_50');
    }

    diagramBox.querySelector('.piece.right').style.transform = 'rotate('+deg+'deg)';
    diagramBox.querySelector('.text b').innerText = seconds;

    setTimeout(function(){
        timer(seconds - 1);
    }, 1000);
}
timer();

Теперь немного подробностей. В коде HTML я изменил только название класса и атрибут data-. Все это исключительно для правильной идентификации и осмысления. В остальном HTML остался неизменен. Но код JS модифицирован чуть больше.

И так, нам понадобится создать функцию timer(), поскольку ее нужно будет вызывать рекурсивно.

Найдем наш элемент с прогресс баром и запишем его в переменную:

let diagramBox = document.querySelector('.diagram.timer');

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

seconds = seconds || diagramBox.dataset.seconds;

Обратите внимание, что теперь нам нужно немного иначе пересчитывать значения для визуального отображения угла поворота блока прогресс бара.

Let deg = (360 * seconds / diagramBox.dataset.seconds) + 180;

Если для отображения процентов в первом примере в атрибуте data- хранилось текущее значение, которое мы делили на статичное максимальное — 100, то в таймере в этом атрибуте хранится теперь максимальное значение, а текущее фигурирует в самом сценарии. Таким образом, максимальное значение может принимать любое положительное число, при этом на диаграмме всегда верно отобразится прогресс.

Далее нужно выполнить ту же функцию, что и в первом примере — определить, когда вспомогательный блок делжен получать класс .over_50 и переворачиваться на другую сторону нашего блока с таймером.

if(seconds >= diagramBox.dataset.seconds / 2){
    diagramBox.classList.add('over_50');
}else{
    diagramBox.classList.remove('over_50');
}

Укажем рассчитанный угол поворота для CSS-свойства transform вращающегося блока и подставим текст текущего значения секунд в тег <b>.

diagramBox.querySelector('.piece.right').style.transform = 'rotate('+deg+'deg)';
diagramBox.querySelector('.text b').innerText = seconds;

И, наконец, нам осталось отнять одну секунду и запустить функцию расчета заново, то есть совершить новую итерацию с задержкой 1000 миллисекунд.

setTimeout(function(){
    timer(seconds - 1);
}, 1000);

Как видите, ничего сложного, по крайней мере для того, кто более-менее знаком с синтаксисом языка, нет.

Прогресс бар в виде круговой диаграммы

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