Красивый индикатор загрузки на чистом CSS
https://t.me/react_tgЕсли у вас есть сайт, вам пригодится индикатор загрузки, чтобы пользователи, нажав на кнопку или ссылку, видели, что что-то происходит.
Этот компонент можно использовать во многих местах. При этом желательно, чтобы он был как можно проще.
В этой статье мы рассмотрим создание индикаторов загрузки двух типов. В обоих случаях мы будем использовать всего один html-элемент <div> и несколько строк CSS-кода. Стоит отметить, что наши индикаторы будут настраиваемыми: вы запросто сможете создать множество их вариаций на основе одного и того же кода.
Вот то, что мы построим:

Как создать спиннер
В 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()
Это небольшие правки для исправления визуальных помех. Градиенты известны тем, что порой производят «странные» результаты. Во избежание этого приходится подгонять некоторые значения вручную.
Как создать индикатор прогресса загрузки
Как и с первым индикатором, давайте начнем с обзора:
У нас здесь та же конфигурация, что в предыдущем индикаторе. 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); 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;}
Обратите внимание, что ширина градиента равна N+1, умноженному на ширину одной полоски, плюс промежутки (вместо 100%).