От автора: я очень долго восхищался дизайном интерфейса в аниме Ghost In The Shell, а на этой неделе понял, что могу воссоздать его в SVG. В данной статье я расскажу пошагово, как я создал такой круговой прогресс-бар.
Готовый пример с полным кодом в CodePen демо.
Разметка
Базовая разметка состоит из нескольких тегов circle разного радиуса, центрированных в одной точке. Также в разметке присутствует тег text:
1 2 3 4 5 6 7 8 9 10 |
<svg viewBox="0 0 100 100" xmlns="//www.w3.org/2000/svg" xmlns:xlink="//www.w3.org/1999/xlink"> <circle cx="50" cy="50" r="22"></circle> <circle cx="50" cy="50" r="24"></circle> <circle cx="50" cy="50" r="26"> </circle> <circle cx="50" cy="50" r="30"></circle> <circle cx="50" cy="50" r="34"></circle> <circle cx="50" cy="50" r="34"></circle> <text x="49" y="54">0</text> </svg> |
Трансформация и анимация в CSS и SVG находится пока что на разных уровнях, поэтому анимацией колец занимается SMIL. Усложняем разметку, добавив тег animate внутрь каждого circle:
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 |
<svg viewBox="0 0 100 100" xmlns="//www.w3.org/2000/svg" xmlns:xlink="//www.w3.org/1999/xlink" id="gits"> <circle cx="50" cy="50" r="22"> <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 50 50" to="360 50 50" dur="10s" repeatCount="indefinite" /> </circle> <circle cx="50" cy="50" r="24"> <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 50 50" to="360 50 50" dur="8s" repeatCount="indefinite" /> </circle> <circle cx="50" cy="50" r="26"> <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 50 50" to="-360 50 50" dur="8s" repeatCount="indefinite" /> </circle> <circle cx="50" cy="50" r="30"> <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 50 50" to="360 50 50" dur="14s" repeatCount="indefinite" /> </circle> <circle cx="50" cy="50" r="34"> <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 50 50" to="360 50 50" dur="18s" repeatCount="indefinite" /> </circle> <circle cx="50" cy="50" r="34"> <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 50 50" to="-360 50 50" dur="20s" repeatCount="indefinite" /> </circle> <text x="49" y="54">0</text> </svg> |
Одни кольца крутятся по часовой стрелке, другие – против. У всех колец разная скорость без заданных повторений (т.е. бесконечно). Обратите внимание, что кольца поделены на 4 части.
Так как кольца у нас не пунктирные, а анимация привязана к центру, то мы пока что не видим самой анимации, даже если добавить стили ниже:
1 2 3 4 5 6 7 8 9 10 11 12 |
circle { stroke: #000; fill: none; stroke-width: 4px; transition: .2s; stroke-dashArray: 0 600; } text { font-family: Titillium Web, sans-serif; font-size: 12px; text-anchor: middle; } |
Чтобы кольца соприкасались друг с другом, но не наезжали, нам достаточно воспользоваться свойством stroke-width. Свойство stroke-dashArray задает высоту обводки такой, чтобы ее не было видно. По крайне мере, сначала. Свойство text-anchor помещает текст в центр прогресс-бара, а transition запускает анимацию.
JavaScript
Кольца закрутились. С помощью JavaScript они превращаются в круговые сегменты:
1 2 |
var circles = document.getElementsByTagName("circle"), progress = document.getElementsByTagName("text")[0]; |
Также нам нужно получить два случайных значения, которые мы возьмем из функции:
1 2 3 |
function getRandomInRange(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } |
Для вычисления длины каждой окружности скрипт использует цикл. Для определения стартового значения stroke-dasharray для каждого круга используется случайное число от 20 до 80, а также длина окружности, которая берется из свойства:
1 2 3 4 5 6 |
for (var j = 0; j < circles.length; ++j) { var radius = parseInt(circles[j].getAttribute('r'), 10); circles[j].circumference = 2 * radius * Math.PI; circles[j].init = getRandomInRange(20,80); circles[j].style.strokeDasharray = circles[j].init + " " + circles[j].circumference; } |
Изначально, в коде использовался цикл for…of, но я узнал, что в Safari данный цикл не проходит по дереву узлов в SVG. Позже я опишу эту проблему. Код ниже формирует все круговые сегменты, а мы видим анимацию.
1 2 3 4 5 6 7 8 9 10 11 |
var i = 0; var timer = setInterval(function() { progress.textContent = i; if (i == 100) { clearInterval(timer); } else { i++; for (var j = 0; j < circles.length; ++j) { circles[j].style.strokeDasharray = circles[j].init + i + " " + circles[j].circumference; } }}, 500) |
В тег text по таймеру каждые 500 миллисекунд (полсекунды) записывается значение i. Это же значение используется для увеличения dashArray каждого круга, что медленно заполняет кольца.
Улучшения
Есть несколько способов:
Не все сегменты превращаются в замкнутые кольца по завершению таймера. Первого значения stroke-dashArray хватает на заполнение маленького внутреннего круга, но его не хватает на формирование замкнутого внешнего кольца. Мне нравится мой код, но вы можете изменить скрипт, чтобы в конце все кольца соединялись.
В идеале, весь SVG код должен генерироваться через JS и заменять HTML5 тег progress, придерживаясь техники прогрессивного улучшения интерфейсов.
Также вы можете изменить скорость роста сегментов таким образом, чтобы все кольца соединялись одновременно.
Источник: //thenewcode.com/
Редакция: Команда webformyself.