Если у вас есть сайт, вам пригодится индикатор загрузки, чтобы пользователи, нажав на кнопку или ссылку, видели, что что-то происходит.
Этот компонент можно использовать во многих местах. При этом желательно, чтобы он был как можно проще.
В этой статье мы рассмотрим создание индикаторов загрузки двух типов. В обоих случаях мы будем использовать всего один html-элемент <div>
и несколько строк CSS-кода. Стоит отметить, что наши индикаторы будут настраиваемыми: вы запросто сможете создать множество их вариаций на основе одного и того же кода.
Вот то, что мы построим:
Как создать спиннер
В CodePen вы видите демо того, что мы создадим:
See the Pen CSS Spinner loader by Temani Afif (@t_afif) on CodePen.
Здесь у нас четыре разных индикатора на основе одного кода. Благодаря изменению всего нескольких переменных мы можем генерировать новый индикатор, не прикасаясь к CSS-коду.
Переменные у нас следующие:
--b
задает толщину черточек--n
— число черточек--g
определяет промежутки между черточками. Поскольку у нас круговой элемент, это значение задается как угол.--c
определяет цвет.
Вот эти переменные на картинке — для лучшего понимания:
Приступаем к CSS-коду. Для иллюстрации пошагового создания индикатора загрузки используем другую фигуру:
Сперва создадим круг:
.loader { width: 100px; /* size */ aspect-ratio: 1; border-radius: 50%; }
Тут пока ничего сложного. Обратите внимание на использование aspect-ratio
. Это позволит менять всего одно значение при изменении размера индикатора.
Затем мы добавляем заливку с коническим градиентом от прозрачного до заданного цвета (переменная --c
):
.loader { width:100px; /* size */ aspect-ratio: 1; border-radius: 50%; background: conic-gradient(#0000,var(--c)); }
На этом этапе мы вводим свойство mask
, чтобы спрятать некоторые части круга (повторяющимся образом). Тут будут применяться переменные --n
и --g
.
Если вы присмотритесь к нашей фигуре, вы заметите следующий шаблон:
видимая часть невидимая часть видимая часть невидимая часть и т. д.
Чтобы достичь такого эффекта, мы используем repeating-conic-gradient(#000 0 X, #0000 0 Y)
.
От 0 до X у нас непрозрачный цвет (видимая часть), а от X до Y — прозрачный (невидимая часть).
Вводим наши переменные:
- Нам нужен промежуток, равный
g
, между видимыми частями, так что формула для видимых частей X и Y будетX = Y - g
. - Нам нужно
n
видимых частей, так что формула Y должна бытьY = 360deg/n
. 360deg — это полный круг (360 градусов), который мы просто делим наn
.
На данный момент у нас следующий код:
.loader { width: 100px; /* size */ aspect-ratio: 1; border-radius: 50%; background: conic-gradient(#0000,var(--c)); mask: repeating-conic-gradient(#000 0 calc(360deg/var(--n) - var(--g)) , #0000 0 calc(360deg/var(--n)) }
Следующий шаг самый заковыристый. Нам нужно применить еще одну маску, чтобы создать как бы отверстие в круге и таким образом получить итоговую форму.
Для этого мы используем radial-gradient()
(что логично) с нашей переменной b
:
radial-gradient(farthest-side,#0000 calc(100% - var(--b)),#000 0)
Это полный круг, из которого мы вычитаем толщину линии, равную b
.
Добавляем это к предыдущей маске:
.loader { width: 100px; /* size */ aspect-ratio: 1; border-radius: 50%; background: conic-gradient(#0000,var(--c)); mask: radial-gradient(farthest-side,#0000 calc(100% - var(--b)),#000 0), repeating-conic-gradient(#000 0 calc(360deg/var(--n) - var(--g)) , #0000 0 calc(360deg/var(--n)) }
Теперь у нас два масочных слоя, но результат не такой, как нам хотелось. Мы получили круг:
Выглядит странно, но на самом деле такой вид совершенно закономерен. «Финальная» видимая часть — не что иное, как сумма всех видимых частей каждого масочного слоя. Это поведение можно изменить при помощи mask-composite
. Об этом свойстве стоит написать отдельную статью, так что здесь я просто приведу значение.
В нашем случае нам нужно значение intersect
(и destination-out
для того же свойства с префиксом). Код становится таким:
.loader { width: 100px; /* size */ aspect-ratio: 1; border-radius: 50%; background: conic-gradient(#0000,var(--c)); mask: radial-gradient(farthest-side,#0000 calc(100% - var(--b)),#000 0), repeating-conic-gradient(#000 0 calc(360deg/var(--n) - var(--g)) , #0000 0 calc(360deg/var(--n)); -webkit-mask-composite: destination-in; mask-composite: intersect; }
С этой фигурой покончено! Только вот индикатор пока без анимации, а должно быть бесконечное вращение.
Обращаю ваше внимание, что для создания иллюзии неподвижных черточек с движущимися цветами я использовал анимацию steps
.
Вот иллюстрация, чтобы вы могли понять разницу:
Первый вариант индикатора сделан при помощи линейной анимации (linear). При этом бесконечно вращается сама фигура (это не то, чего бы нам хотелось).
Во втором варианте анимация прерывистая (как нам и нужно).
Вот полный код, включая анимацию:
<div class="loader"></div> <div class="loader" style="--b: 15px;--c: blue;width: 120px;--n: 8"></div> <div class="loader" style="--b: 5px;--c: green;width: 80px;--n: 6;--g: 20deg"></div> <div class="loader" style="--b: 20px;--c: #000;width: 80px;--n: 15;--g: 7deg"></div>
.loader { --b: 10px; /* border thickness */ --n: 10; /* number of dashes*/ --g: 10deg; /* gap between dashes*/ --c: red; /* the color */ width: 100px; /* size */ aspect-ratio: 1; border-radius: 50%; padding: 1px; background: conic-gradient(#0000,var(--c)) content-box; -webkit-mask: repeating-conic-gradient(#0000 0deg, #000 1deg calc(360deg/var(--n) - var(--g) - 1deg), #0000 calc(360deg/var(--n) - var(--g)) calc(360deg/var(--n))), radial-gradient(farthest-side,#0000 calc(98% - var(--b)),#000 calc(100% - var(--b))); mask: repeating-conic-gradient(#0000 0deg, #000 1deg calc(360deg/var(--n) - var(--g) - 1deg), #0000 calc(360deg/var(--n) - var(--g)) calc(360deg/var(--n))), radial-gradient(farthest-side,#0000 calc(98% - var(--b)),#000 calc(100% - var(--b))); -webkit-mask-composite: destination-in; mask-composite: intersect; animation: load 1s infinite steps(var(--n)); } @keyframes load {to{transform: rotate(1turn)}}
Вы заметите, что тут кое-что отличается от моих объяснений:
- Я добавил
padding: 1px
и установилcontent-box
вbackground
- Здесь есть
+/1deg
между цветамиrepeating-conic-gradient()
- Также здесь несколько процентов разницы между цветами внутри
radial-gradient()
Это небольшие правки для исправления визуальных помех. Градиенты известны тем, что порой производят «странные» результаты. Во избежание этого приходится подгонять некоторые значения вручную.
Как создать индикатор прогресса загрузки
Как и с первым индикатором, давайте начнем с обзора:
See the Pen progress CSS loader by Temani Afif (@t_afif) on CodePen.
У нас здесь та же конфигурация, что в предыдущем индикаторе. CSS-переменные для управления лоадером:
--n
определяет число черточек/полосок--s
определяет ширину каждой полоски--g
задает пробел между полосками
Мы видим, что ширина всего элемента зависит от трех переменных. Наш CSS-код будет следующим:
.loader { width: calc(var(--n)*(var(--s) + var(--g)) - var(--g)); height: 30px; /* use any value you want here */ padding: var(--g); border: 1px solid; }
Для создания промежутков со всех сторон мы используем padding
. Наша ширина будет равна числу полосок, умноженному на ширину полосок, плюс промежутки. Один промежуток мы удаляем, потому что при N
полосок у нас N-1
промежутков между ними.
Для создания полосок мы используем следующий градиент:
repeating-linear-gradient(90deg, currentColor 0 var(--s), #0000 0 calc(var(--s) + var(--g)) )
Здесь от 0 до s
— заданный цвет, а от s
до s + g
— прозрачный цвет (промежуток).
currentColor
, который я использую, это значение свойства color
. Обратите внимание, что я не определял никакой цвет внутри border
, так что здесь тоже будет использоваться значение color
.
Наш код на данный момент:
.loader { width: calc(var(--n)*(var(--s) + var(--g)) - var(--g)); height: 30px; padding: var(--g); border: 1px solid; background: repeating-linear-gradient(90deg, currentColor 0 var(--s), #0000 0 calc(var(--s) + var(--g)) ) left / 100% 100% content-box no-repeat; }
Благодаря использованию content-box
градиент не закроет зону padding
.
Затем я определяю размер как 100% 100% и позицию left.
Пора заняться анимацией.
В этом индикаторе мы будем анимировать background-size
от 0% 100%
до 100% 100%
, т. е. ширину нашего градиента от 0% до 100%.
Как и в предыдущем случае, мы применим steps()
, чтобы получить не плавную, а прерывистую анимацию.
Чтобы добиться такого эффекта, как на второй картинке, добавим следующий код:
.loader { animation: load 1.5s steps(var(--n)) infinite; } @keyframes load { 0% {background-size: 0% 100%} }
Присмотревшись к нашей гифке, вы заметите, что анимация неполная. Мы пропускаем одну полоску в конце, хотя используем N
. Это не баг, именно так и работает steps()
.
Чтобы это исправить, нужно добавить дополнительный шаг. Мы увеличим background-size
нашего градиента, чтобы там было N+1
полосок, и используем steps(N+1)
.
В итоге наш код будет выглядеть так:
.loader { width: calc(var(--n)*(var(--s) + var(--g)) - var(--g)); height: 30px; padding: var(--g); margin: 5px auto; border: 1px solid; background: repeating-linear-gradient(90deg, currentColor 0 var(--s), #0000 0 calc(var(--s) + var(--g)) ) left / calc((var(--n) + 1)*(var(--s) + var(--g))) 100% content-box no-repeat; animation: load 1.5s steps(calc(var(--n) + 1)) infinite; } @keyframes load { 0% {background-size: 0% 100%} }
Обратите внимание, что ширина градиента равна N+1
, умноженному на ширину одной полоски, плюс промежутки (вместо 100%).
Перевод статьи «How to Create a CSS-Only Loader Using One Element».
[customscript]techrocks_custom_after_post_html[/customscript]
[customscript]techrocks_custom_script[/customscript]