Skip to Content

Лекция 05. CSS: адаптивность, состояния и анимация

На прошлой лекции мы собрали шапку и сетку карточек с помощью Flexbox и Grid. Но что произойдёт, если открыть страницу на телефоне? А если пользоваться ею с клавиатуры? Как показать, что кнопка реагирует на действие пользователя, не отвлекая его постоянным движением?

Сегодня доведём интерфейс до состояния, в котором им удобно пользоваться на разных экранах и с разными способами управления.

После этой лекции

Вы сможете проверить страницу на узком экране, изменить раскладку с помощью медиазапроса, оформить состояния ссылки и кнопки, добавить плавный переход и простую анимацию. А ещё — объяснить, почему красивый эффект не должен мешать чтению и управлению.


1. Адаптивность: одна страница, разные условия

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

Начнём с настройки в <head>:

<meta name="viewport" content="width=device-width, initial-scale=1">

Она сообщает мобильному браузеру, что ширину области просмотра нужно согласовать с шириной устройства. Без неё браузер может показать страницу как широкую настольную версию, уменьшенную целиком.

Область просмотра (viewport) — видимая область страницы в окне браузера. При изменении размера окна её ширина меняется, даже если устройство остаётся тем же.

Ограничиваем ширину содержимого

.container { width: min(100% - 32px, 1100px); margin-inline: auto; }

На узком экране контейнер оставит по 16 пикселей по сторонам, а на широком не станет шире 1100 пикселей. margin-inline: auto центрирует его по горизонтали в привычном нам направлении письма.

Сравните с width: 1100px: фиксированный контейнер может оказаться шире окна и вызвать горизонтальную прокрутку. Ограничение ширины не гарантирует отсутствие прокрутки: её может создать и содержимое, например длинная непрерывная строка.

Для картинок полезно задать:

img { max-width: 100%; height: auto; }

Изображение не станет шире доступного места, а его пропорции сохранятся. Это правило уменьшает большую картинку, но само по себе не растягивает маленькую на всю ширину.

Проверьте себя: что произойдёт с контейнером шириной ровно 1100px в окне шириной 375px? Почему ограничение max-width для изображения может помочь, но не исправит любой источник переполнения?

2. Медиазапросы: меняем раскладку, когда ей тесно

Медиазапрос позволяет применить CSS только при выполнении условия. Например, начнём с одной колонки и добавим вторую, когда области просмотра станет достаточно:

.cards { display: grid; grid-template-columns: 1fr; gap: 16px; } @media (min-width: 700px) { .cards { grid-template-columns: repeat(2, 1fr); } }

До 700 пикселей карточки стоят друг под другом. При ширине области просмотра от 700 пикселей включительно действует правило с двумя колонками. Оба правила подходят .cards, но внутри медиазапроса второе записано позже и переопределяет число колонок.

Такой подход часто называют mobile first: базовые стили рассчитаны на узкое пространство, затем добавляются правила для более широкого. Можно писать и наоборот через max-width; важно понимать, какие правила действуют одновременно и какое из них победит в каскаде.

Число 700 здесь — пример, а не граница всех телефонов. Выбирайте точку изменения там, где конкретной раскладке становится тесно: меняйте ширину окна и наблюдайте за содержимым. Учитывайте, что ширина viewport и ширина контейнера не обязательно совпадают.

Гибкая сетка без медиазапроса

Иногда сетка может сама выбирать число колонок:

.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); gap: 16px; }

auto-fit подбирает количество колонок по доступному месту. minmax задаёт их размеры: колонка стремится быть не уже 260 пикселей, но на совсем узком экране может занять 100% доступной ширины. Свободное место распределяется между колонками через 1fr. Здесь мы ориентируемся на ширину самой сетки, а медиазапрос выше — на ширину viewport.

Это не обязательная замена медиазапросам: если нужно одновременно перестроить навигацию, изменить отступы и порядок блоков, явное условие может быть понятнее.

Попробуйте: растяните окно от узкого до широкого. В какой момент две карточки становятся удобнее одной? Измените текст заголовка карточки — по-прежнему ли сетка работает?

3. Состояния: интерфейс отвечает пользователю

Ссылка или кнопка может выглядеть по-разному в зависимости от действия пользователя. Для этого применяют псевдоклассы — например, :hover, :focus-visible и :active.

<a class="button" href="#materials">Смотреть материалы</a>
.button { display: inline-block; padding: 12px 20px; border-radius: 8px; background: #174ea6; color: white; text-decoration: none; } .button:hover { background: #123b7e; } .button:focus-visible { outline: 3px solid #e67e00; outline-offset: 3px; } .button:active { background: #0c2a5a; }
  • :hover обычно срабатывает, когда указатель находится над элементом. На сенсорных экранах поведение может отличаться, поэтому не прячьте важные действия только за наведением.
  • :focus-visible показывает фокус, когда браузер определяет, что пользователю нужен видимый индикатор, например при переходе клавишей Tab.
  • :active действует во время активации элемента, например пока кнопка мыши нажата.

Проверим: нажмите Tab несколько раз. У ссылки должна появиться заметная рамка фокуса. Затем нажмите Enter, чтобы перейти к разделу. Не убирайте стандартную обводку через outline: none, если не предоставили достаточно заметную замену.

Ссылка подходит для перехода к адресу или разделу. Для действия на странице, например открытия меню, нужен <button type="button">. Один CSS может визуально сделать ссылку похожей на кнопку, но не меняет её назначение.

Вопрос: будет ли пользователь сенсорного экрана всегда видеть :hover? Как он узнает о действии, если подсказка появляется только при наведении?

4. Плавный переход с transition

Сейчас цвет ссылки меняется сразу. Добавим плавность:

.button { transition: background-color 180ms ease; }

transition задаёт свойство, длительность и характер изменения. Здесь браузер плавно меняет background-color за 180 миллисекунд при переходе между состояниями. Обратите внимание: переход записан в основном правиле .button, поэтому работает и при наведении, и при уходе указателя.

Не обязательно анимировать всё через transition: all: перечисление нужных свойств делает результат предсказуемее. Переход не создаёт действие сам по себе — он лишь смягчает изменение значения, которое произошло из-за другого правила.

5. Анимация с @keyframes

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

.notice { animation: appear 300ms ease-out; } @keyframes appear { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }

@keyframes описывает начало и конец, а animation привязывает их к элементу. opacity меняет прозрачность, translateY — визуальное смещение по вертикали. Здесь анимация запускается при применении класса и проигрывается один раз; CSS не создаёт уведомление и не управляет моментом его добавления на страницу.

Чем отличается от перехода? transition плавно сопровождает смену значения, например при наведении. animation позволяет задать самостоятельную последовательность кадров и, при необходимости, повторение. Для обычной реакции кнопки чаще достаточно перехода.

Не делайте важное сообщение читаемым только во время анимации. Движение должно помогать заметить изменение, а не мешать восприятию содержания.

Учитываем настройку уменьшения движения

Некоторые пользователи просят систему уменьшить движение. Учтём это для наших примеров:

@media (prefers-reduced-motion: reduce) { .button { transition: none; } .notice { animation: none; } }

Без анимации уведомление остаётся видимым: прозрачность в обычном состоянии не задана, поэтому используется значение по умолчанию 1. Кнопка при этом продолжает менять цвет и показывать фокус — только без плавного перехода.

6. Собираем вместе

Представим раздел с материалами. Разметку можно сохранить в index.html, стили — в styles.css рядом с ним.

<!doctype html> <html lang="ru"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Учебные материалы</title> <link rel="stylesheet" href="styles.css"> </head> <body> <main class="container" id="materials"> <h1>Учебные материалы</h1> <p>Выберите тему и откройте конспект.</p> <div class="cards"> <article class="card"> <h2>Основы HTML</h2> <p>Структура страницы и семантика.</p> <a class="button" href="html.html">Открыть HTML</a> </article> <article class="card"> <h2>Основы CSS</h2> <p>Оформление и раскладка элементов.</p> <a class="button" href="css.html">Открыть CSS</a> </article> </div> </main> </body> </html>
* { box-sizing: border-box; } body { margin: 0; font-family: sans-serif; line-height: 1.5; color: #172033; } .container { width: min(100% - 32px, 1100px); margin-inline: auto; padding-block: 32px; } .cards { display: grid; grid-template-columns: 1fr; gap: 16px; } .card { padding: 20px; border: 1px solid #c7d1e0; border-radius: 12px; } .card h2 { margin-top: 0; } .button { display: inline-block; padding: 12px 20px; border-radius: 8px; background: #174ea6; color: white; text-decoration: none; transition: background-color 180ms ease; } .button:hover { background: #123b7e; } .button:focus-visible { outline: 3px solid #e67e00; outline-offset: 3px; } .button:active { background: #0c2a5a; } @media (min-width: 700px) { .cards { grid-template-columns: repeat(2, 1fr); } } @media (prefers-reduced-motion: reduce) { .button { transition: none; } }

Ссылки html.html и css.html приведены как пример: чтобы переходы работали, создайте эти файлы или замените адреса на существующие. Измените ширину окна, проверьте наведение и пройдите по ссылкам клавишей Tab. В настройках системы или инструментах разработчика включите уменьшение движения и убедитесь, что смена цвета происходит без перехода.

7. Как проверять адаптивную страницу

  1. Откройте инструменты разработчика браузера и включите режим просмотра на устройствах. Начните с узкого экрана, затем постепенно расширяйте область просмотра.
  2. Проверьте, нет ли горизонтальной прокрутки и не обрезаются ли текст, изображения или элементы управления. Найдите причину переполнения, прежде чем скрывать его.
  3. Увеличьте масштаб страницы и проверьте, что содержание остаётся доступным.
  4. Пройдите интерактивные элементы с клавиатуры: виден ли фокус и понятен ли порядок перехода?
  5. Проверьте настройки уменьшения движения и убедитесь, что информация и действия по-прежнему доступны.

Эмуляция в браузере полезна, но не заменяет проверку на реальном устройстве: размеры экрана, ввод касанием и поведение браузеров могут различаться.

Проверьте себя

  1. Зачем нужна настройка viewport и почему она сама по себе не делает страницу адаптивной?
  2. Чем ширина viewport отличается от ширины контейнера?
  3. Какие стили сработают при ширине ровно 700px в нашем примере с min-width?
  4. Почему важное действие нельзя показывать только при :hover?
  5. Когда используют transition, а когда могут пригодиться @keyframes?
  6. Что должно остаться доступным после отключения анимации?

Главное

Адаптивность начинается с гибкого содержимого и проверки при разных размерах, а медиазапрос помогает менять правила там, где это нужно. Состояния сообщают об интерактивности и помогают управлять страницей с клавиатуры. Переходы и анимация делают изменения заметнее, если не мешают пользователю и учитывают его настройки.