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

Спиннер загрузки собирается без единой картинки: круг из border с одной стороной другого цвета плюс @keyframes, крутящий его через rotate(0deg) - rotate(360deg) по кругу. Важная деталь, которую легко упустить: timing-function обязательно linear, иначе вращение выглядит рывками. И раз спиннер - это просто крутящийся div, для незрячих пользователей он ничего не значит без role="status" и скрытого текста.

Попробуй прямо сейчас: круг крутится ровно, без рывков - это заслуга animation-timing-function: linear. Поменяй linear на ease (или удали строку целиком - ease станет значением по умолчанию) и вращение станет неровным: то ускоряется, то замедляется, что для спиннера выглядит как техническая проблема, а не задуманный эффект. Обрати внимание и на текст "Загрузка..." внутри - глазами он не виден (спрятан классом .visually-hidden), но именно он объясняет экранному диктору, что вообще происходит на странице.

Спиннер из одной обводки: border-trick и rotate

Сам круг - это div с одинаковыми width и height, border-radius: 50% превращает квадрат в окружность. "Вращающийся" эффект даёт не движение, а обычная одноцветная обводка border, у которой ОДНА сторона (например border-top) перекрашена в другой, более яркий цвет через border-top-color или отдельное свойство border-top. Дальше @keyframes поворачивает весь элемент через transform: rotate() от 0deg до 360deg, и раз обводка неравномерная, вращение самого div создаёт иллюзию "бегущего" по кругу сегмента - хотя на самом деле крутится вся обводка целиком.

.spinner { width: 48px; height: 48px; border: 5px solid #e5e7eb; border-top: 5px solid #4f46e5; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } - iteration-count: infinite здесь часть шорткада animation, крутится, пока страница открыта (уже разбирали в уроке про animation-duration и iteration-count).

.spinner { border: 5px solid #e5e7eb; border-top: 5px solid #4f46e5; border-radius: 50%; animation: spin 1s infinite; } - timing-function не указан, по умолчанию ease: угол поворота растёт неравномерно, вращение выглядит рваным вместо ровного.

Спиннер и доступность: role="status" и prefers-reduced-motion

Для зрячего пользователя крутящийся кружок и так понятен без слов. Но для человека с экранным диктором пустой вращающийся div - это просто ничего, диктор его не озвучит и не расскажет, что страница загружается. Стандартное решение - role="status" на контейнере спиннера (у него уже встроено aria-live="polite" - объявление придёт, когда пользователь не занят чем-то другим, а не перебьёт его на середине действия) и текст внутри вроде "Загрузка...", спрятанный от глаз классом .visually-hidden, но доступный диктору.

<div class="spinner" role="status"> <span class="visually-hidden">Загрузка...</span> </div> - роль status плюс скрытый текст: диктор скажет "Загрузка...", зрячий пользователь увидит только кружок.

<div class="spinner"></div> - без role и текста внутри диктор просто промолчит, будто на странице ничего не происходит.

Отдельный вопрос - нужно ли отключать вращение спиннера для пользователей, попросивших "меньше анимации" в настройках системы через @media (prefers-reduced-motion: reduce) - об этом упоминалось в прошлом уроке. Спиннер отличается от декоративных эффектов появления тем, что он функциональный - это единственный видимый сигнал "страница ещё не готова", поэтому многие сайты сознательно оставляют его вращаться даже при включённой настройке, отключая через reduced-motion только чисто декоративные анимации.

Частые ошибки новичков

ОшибкаЧто происходитКак исправить
Не указывать animation-timing-function: linear для вращения спиннера По умолчанию применяется ease - вращение выглядит рваным, то быстрее, то медленнее Всегда явно указывать linear для бесконечного равномерного вращения
Оставлять спиннер пустым div-ом без role="status" и текста внутри Пользователь с экранным диктором не узнаёт, что страница загружается Добавлять role="status" и визуально скрытый текст вроде "Загрузка..."
Использовать role="alert" вместо role="status" для обычного индикатора загрузки alert - для срочных прерывающих сообщений (ошибок), не для рутинной загрузки Использовать role="status" - его "вежливое" aria-live="polite" подходит лучше
Полностью убирать анимацию спиннера через animation: none, если на неё завязан JS-код (например ждёт animationend) Событие animationend перестаёт срабатывать, JS-логика ломается Сокращать animation-duration до 1ms вместо полного отключения, если событие важно

Шпаргалка

ЗаписьЧто означает
border + border-top другого цвета + border-radius:50%Круг с "бегущим" сегментом без единой картинки
animation: spin 1s linear infinite;Ровное бесконечное вращение - linear обязателен
animation-timing-function: ease (по умолчанию)Вращение выглядит рваным - не годится для спиннера
role="status" + .visually-hidden текстЭкранный диктор объявит статус загрузки
role="alert"Для срочных ошибок, не для обычной загрузки

Коротко: отвечаем на вопросы

Почему у спиннера обязательно должен быть linear, а не ease?

При linear угол поворота растёт строго пропорционально времени - равные промежутки времени дают равные приросты угла, поэтому вращение выглядит ровным и предсказуемым. При ease (значение по умолчанию, если не указать timing-function явно) скорость вращения меняется на каждом обороте - на бесконечном вращении это не тонкая деталь, а сразу заметная рваная, дёрганая анимация вместо плавного кручения.

Нужно ли отключать вращение спиннера для пользователей с prefers-reduced-motion: reduce?

Это не жёсткое правило, а решение конкретного сайта. Спиннер отличается от декоративных эффектов появления тем, что он функциональный - это единственный видимый сигнал "страница ещё не готова", поэтому многие сайты сознательно оставляют его вращаться даже при включённой настройке "меньше анимации", отключая через reduced-motion только декоративные эффекты без функциональной нагрузки.

Категория
Поделиться в социальных сетях
1. Переходы: transition
2. Трансформации: transform
3. Ключевые кадры и animation
4. Фильтры: filter
5. Практика: живые интерфейсы
6. Производительность и доступность

Отправить комментарий

0 Комментарии

Отправить комментарий (0)

#buttons=(Хорошо) #days=(20)

Мы используем файлы cookie для улучшения работы сайта. Подробнее
Хорошо
×