Индикатор загрузки на чистом CSS

0
2894
views
verstka logo

Хочешь знать больше про веб?

Подпишись на наш телеграм-канал TechRocks WEB-разработка?

×
Image by Werner Moser from Pixabay

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

Этот компонент можно использовать во многих местах. При этом желательно, чтобы он был как можно проще.

В этой статье мы рассмотрим создание индикаторов загрузки двух типов. В обоих случаях мы будем использовать всего один 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. Об этом свойстве стоит написать отдельную статью, так что здесь я просто приведу значение.

В нашем случае нам нужно значение intersectdestination-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».

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here