Красивый индикатор загрузки на чистом CSS

Красивый индикатор загрузки на чистом 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%).


Report Page