Веб‑оптимизация
Веб‑оптимизация — это не один конкретный подход, а целый набор операций и действий, которые мы прменяем, чтобы ускорить и повысить стабильность работы веб-систем. Мы стараемся, чтобы страница открывалась быстро, вела себя предсказуемо и не «прыгала» при загрузке, а пользователь получал контент ровно тот контент, который и задумывался.
В современной разработке это “качество” принято оценивать набором метрик Core Web Vitals (выделяет Google), которые описывают три ключевых аспекта: скорость появления основного контента (LCP — Largest Contentful Paint), отзывчивость интерфейса (INP — Interaction to Next Paint) и стабильность макета (CLS — Cumulative Layout Shift).
Традиционно выделяется зеленая, желтая и красная зона для каждого из показателей.
Адаптивный дизайн - это база
Скорость — это не только «сколько секунд грузится страница», но и насколько удобно ей пользоваться на конкретном устройстве. Если интерфейс сжался в нечитаемое месиво или кнопки стало невозможно нажать пальцем, чисто технически страница может быть быстрой, но пользы от этого мало.
Поэтому первым слоем оптимизации всегда идёт адаптивная вёрстка. Она опирается на три вещи:
-
корректный meta‑тег
viewportв<head>:<meta name="viewport" content="width=device-width, initial-scale=1">Без него мобильный браузер будет пытаться «сжимать» десктопную версию, и все медиазапросы по ширине станут работать странно.
-
грамотно настроенные медиазапросы, которые управляют расположением блоков в зависимости от ширины экрана;
-
гибкие единицы измерения — проценты,
rem,vw,vh.
Самый просто пример - сетка с карточками. На широком экране одна строка показывает четыре элемента, на планшете — две, на телефоне — одну. Это легко описывается одним правилом display: grid и несколькими медиазапросами:
.gallery {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24px;
}
@media (max-width: 1024px) {
.gallery {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 600px) {
.gallery {
grid-template-columns: 1fr;
}
}В результате одна и та же разметка работает на всех устройствах, а браузеру не нужно перерисовывать страницу сложными скриптами после загрузки.
Возможности устройства: hover, pointer и анимации
Медиазапросы умеют смотреть не только на ширину экрана. Через hover и pointer можно понять, навигация осуществляется мышью или пальцем. На десктопе эффекты наведения (:hover) выглядят естественно, а на смартфоне они часто воспринимаются как странный псевдо‑клик.
Поэтому, при помощи конструкции @media (hover: hover) and (pointer: fine) можно добиться того, чтобы все внутреннее содержимое срабатывало только в том случае, если использовается устройство с экранным курсором:
@media (hover: hover) and (pointer: fine) {
.art-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 20px rgba(0, 0, 0, .15);
}
}Точно так же prefers-reduced-motion даёт возможность по‑другому вести себя с анимациями для пользователей, которые по разным причинам отключили их (такая возможность предоставляется в настройках браузера):
@media (prefers-reduced-motion: reduce) {
* {
scroll-behavior: auto;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}Такие настройки одновременно и гуманны, и полезны для производительности: меньше анимаций — меньше работы для CPU и GPU, поэтому важно уметь соблюдать баланс.
Светлая и тёмная тема
Темы оформления — ещё один слой оптимизации, но уже визуальной. При правильной реализации переключение между светлой и тёмной темой практически не стоит производительности, но сильно улучшает восприятие сайта.
Современный подход строится вокруг трёх вещей:
- CSS‑переменные для цветовой палитры;
- медиазапрос
prefers-color-scheme, который подглядывает в настройки системы; - свойство
color-scheme, сообщающее браузеру, что страница поддерживает и светлый, и тёмный варианты.
Скелет решения, которое поддерживает оба решения, выглядит так:
:root {
--bg: #ffffff;
--text: #111111;
--card-bg: #f5f5f5;
--accent: #4b6fff;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #050508;
--text: #f4f4f4;
--card-bg: #141420;
--accent: #9ab4ff;
}
}
body {
background-color: var(--bg);
color: var(--text);
color-scheme: light dark;
}Дальше всё оформление — фоны карточек, цвет рамок, ссылки, бордеры кнопок — завязывается на эти переменные. В результате тема автоматически переключается следом за системой, а если понадобится вручную управлять режимом, достаточно будет поменять пару переменных внутри [data-theme="dark"].
color-scheme в том, что браузер сам перекрашивает встроенные элементы: скроллбар, формы, системные диалоги.Пример с полноценным переключением темы
- Добавим кнопку переключатель:
<button id="theme-toggle" type="button" aria-label="Переключить тему">
<span class="theme-toggle__icon theme-toggle__icon--light" aria-hidden="true">🌞</span>
<span class="theme-toggle__icon theme-toggle__icon--dark" aria-hidden="true">🌙</span>
</button>- Связываем тему с
data-theme:
:root {
--bg: #ffffff;
--text: #111111;
--card-bg: #f5f5f5;
--accent: #4b6fff;
color-scheme: light; /* по умолчанию светлая */
}
/* Автоматический тёмный режим — если пользователь так настроил систему */
@media (prefers-color-scheme: dark) {
:root {
--bg: #050508;
--text: #f4f4f4;
--card-bg: #141420;
--accent: #9ab4ff;
color-scheme: dark;
}
}
:root[data-theme="light"] {
--bg: #ffffff;
--text: #111111;
--card-bg: #f5f5f5;
--accent: #4b6fff;
color-scheme: light;
}
:root[data-theme="dark"] {
--bg: #050508;
--text: #f4f4f4;
--card-bg: #141420;
--accent: #9ab4ff;
color-scheme: dark;
}
body {
background-color: var(--bg);
color: var(--text);
}
#theme-toggle {
border: 1px solid var(--accent);
background: transparent;
border-radius: 999px;
padding: 0.25rem 0.6rem;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 0.25rem;
font-size: 1rem;
}
.theme-toggle__icon--dark {
display: none;
}
/* Когда тема тёмная — показываем луну, прячем солнце */
:root[data-theme="dark"] .theme-toggle__icon--light {
display: none;
}
:root[data-theme="dark"] .theme-toggle__icon--dark {
display: inline;
}- если нет
data-theme→ работаетprefers-color-scheme(авто по системе); - если пользователь кликнул переключатель →
data-theme="light"или"dark"перебивают медиа-запрос.
- JS: переключение и сохранение в
localStorageСкрипт лучше положить в конецbody, после кнопки:
<script>
(function () {
const storageKey = 'theme';
const root = document.documentElement;
const toggleBtn = document.getElementById('theme-toggle');
// Что сейчас выбрано в локальном хранилище
const getStoredTheme = () => localStorage.getItem(storageKey);
// Что говорит система (если пользователь ничего не выбирал на сайте)
const getSystemTheme = () =>
window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
// Применяем тему и сохраняем выбор
const applyTheme = (theme) => {
if (theme === 'light' || theme === 'dark') {
root.setAttribute('data-theme', theme);
localStorage.setItem(storageKey, theme);
} else {
// вариант: “как в системе”
root.removeAttribute('data-theme');
localStorage.removeItem(storageKey);
}
};
// При первом заходе: берём либо прошлый выбор, либо системную тему
const initialTheme = getStoredTheme() || getSystemTheme();
applyTheme(initialTheme);
// Клик по кнопке: переключаем значение
toggleBtn.addEventListener('click', () => {
const current =
root.getAttribute('data-theme') || getSystemTheme();
const next = current === 'dark' ? 'light' : 'dark';
applyTheme(next);
});
})();
</script>Изображения - гравная нагрузка на странице
Практически на любом сайте изображения составляют львиную долю от всего загружаемого контента. Если загрузить их «как есть» в PNG по 5–10 МБ, мы создадим очень серьезную нагрузку на сеть потребителя (особенно учитывая как часто у нас с ней в последнее время происходят перебои).
Здесь важно учитывать сразу несколько факторов:
- формат;
- физический размер (ширина/высота в пикселях);
- правила загрузки (адаптивность и откладывание).
Формат — выбор между JPEG/PNG, SVG и современными WebP/AVIF. JPEG хорошо подходит для фотографий и «живописи», PNG — для картинок с прозрачностью и чёткими границами, SVG — для логотипов и иконок. WebP и AVIF чаще всего позволяют получить то же самое качество при заметно меньшем весе, поэтому их удобно объявлять через <picture> как основной вариант, оставляя JPEG в качестве запасного:
<picture>
<source srcset="/images/monet.webp" type="image/webp">
<img src="/images/monet.jpg" alt="Картина Клода Моне">
</picture>Следующий шаг — адаптивные изображения. Смартфону не нужен баннер в 2000 пикселей шириной, он всё равно «сожмёт» его до ширины экрана. Лучше сразу дать браузеру несколько вариантов через srcset и sizes, чтобы он сам выбрал подходящую комбинацию размера и плотности пикселей:
<img
src="/images/gallery-640.jpg"
srcset="
/images/gallery-320.jpg 320w,
/images/gallery-640.jpg 640w,
/images/gallery-1280.jpg 1280w
"
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 33vw"
alt="Экспозиция виртуального музея"
/>Наконец, мы можем отложить загрузку всех изображений, которые находятся ниже первого экрана. Современным браузерам достаточно указать loading="lazy":
<img src="/images/sculpture.webp" loading="lazy" alt="Скульптура из коллекции">Таким образом, при первом заходе пользователь получает только логотип, шапку, главный баннер и первые несколько картин — остальное догружается по мере прокрутки. Это сильно экономит трафик и улучшает скорость.
loading="lazy". Для неё уместнее связка preload + fetchpriority="high", чтобы браузер понял, что именно этот ресурс важнее остальных.А что делать с типографикой?
Второй крупный источник проблем с перфомансом — веб‑шрифты. Они помогают создать настроение сайта, но при неаккуратной настройке легко добавляют по несколько сотен килобайт и портят как скорость появления текста, так и стабильность макета.
Базовая схема подключения локального шрифта выглядит так:
@font-face {
font-family: 'MuseumSans';
src: url('/fonts/MuseumSans-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
body {
font-family: 'MuseumSans', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
}Здесь сразу несколько важных моментов:
- формат
woff2даёт компактный размер и хорошую поддержку; font-display: swapпозволяет сразу показать системный шрифт и заменить его на веб‑шрифт, как только тот загрузится, что убирает «мигание невидимого текста» и улучшает CLS;- указание системных шрифтов в конце даёт аккуратный fallback на случай, если веб‑шрифт вообще не загрузится.
Еще один вытекающий вопрос: брать шрифты с CDN или хранить локально? С CDN (например, Google Fonts) всё очень просто: добавили <link> — и всё заработало. Плюс высокие шансы, что шрифт уже в кэше пользователя. Но есть и обратная сторона: зависимость от стороннего домена, дополнительные сетевые рукопожатия, возможные вопросы конфиденциальности и меньше контроля над подмножествами и обновлениями.
Локальное подключение через @font-face требует чуть больше настроек, зато:
- легко сделать подмножества (например, отдельный файл только для латиницы и отдельный — для кириллицы);
- можно настроить долгосрочное кэширование на своём сервере;
- проще управлять количеством начертаний и весом каждого файла.
Обычно разумно ограничиться двумя‑тремя начертаниями (обычный + полужирный, возможно, курсив) и одним‑двумя семействами. Подмножества и формат woff2 в сочетании с font-display: swap дают хорошее сочетание красоты и скорости.
Отдельный хороший приём — предзагрузка критического шрифта. Если основная типографика страницы зависит от конкретного начертания, его можно объявить в <head>:
<link
rel="preload"
href="/fonts/MuseumSans-Regular.woff2"
as="font"
type="font/woff2"
crossorigin
>Так браузер начнёт качать файл ещё до парсинга всех CSS, а текст появится быстрее.
Управление загрузкой ресурсов и кэшированием
На финальном слое оптимизации мы уже не меняем разметку или стили, а подсказываем браузеру, что и когда нужно грузить, и на какое время можно складывать это в кэш.
Специальные HTML‑подсказки, называемые resource hints, помогают здесь довольно сильно. preload говорит браузеру: «этот файл точно понадобится — загрузи его как можно раньше», preconnect и dns-prefetch заранее устанавливают сетевые соединения с внешними доменами, а prefetch даёт нам возможность тихо подготовить ресурсы для следующей страницы.
Например, для часто имеет смысл:
- предзагрузить CSS‑файл темы и веб‑шрифт;
- предзагрузить hero‑изображение первого экрана;
- для внешнего CDN (если он всё‑таки используется) заранее выполнить
preconnect:
<link rel="preload" href="/css/theme.css" as="style">
<link rel="preload" href="/fonts/MuseumSans-Regular.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="/images/hero.webp" as="image">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>Дополнительно мы можем управлять приоритетом запросов через Fetch Priority API, указывая fetchpriority="high" для действительно важного изображения или скрипта. Это особенно полезно для улучшения LCP.
Параллельно на сервере настраивается HTTP‑кэширование: статические файлы (картинки, шрифты, сборки JS/CSS с хешами в имени) получают долгий Cache-Control: max-age=31536000, immutable, а HTML‑страницы и API могут иметь короткий или «умный» кэш с проверкой актуальности.
Все эти пункты следуют пути критического пути рендеринга: чем меньше ресурсов браузер обязан обработать до того, как показать первый экран, тем быстрее страница «оживает». Остальные картинки, скрипты и виджеты спокойно догружаются позже, не мешая пользователю рассматривать картины.
Практическое задание - «виртуальный музей»
Создать адаптивный сайт на тему “Виртуальный музей искусства”. Сайт должен представлять коллекцию художественных произведений с различными секциями, такими как “Картины”, “Скульптуры”, “Современное искусство” и “Интерактивные выставки”.
В него необходимо включить все те способы оптимизации, которые мы с вами сегодня обсуждали:
- макет адаптируется к разным экранам через медиазапросы;
- эффекты (
:hover) и анимации учитывают возможности устройства (keyframesиtransform) и системные настройки; - изображения подготовлены в подходящих форматах, отданы в нескольких размерах и подгружаются по мере надобности;
- шрифты подключены так, чтобы текст был виден сразу, а сами файлы кэшировались;
- ключевые ресурсы первого экрана (CSS, шрифты, hero‑изображение) подхватываются с помощью
preload, а остальные аккуратно кэшируются. - реализовать темную и светлую тему сайта с использованием медиазапросов, CSS переменных и атрибута
color-scheme.
Групповые доклады
Доклад 1: Современные методы сжатия изображений
- Сравнение форматов изображений (JPEG, PNG, WebP, AVIF).
- Инструменты и методы сжатия изображений без потери качества.
- Примеры улучшения производительности.
Доклад 2: Оптимизация загрузки шрифтов
- Влияние шрифтов на производительность сайта.
- Подмножество шрифтов, формат WOFF2, задержка загрузки.
- Использование
font-displayдля управления отображением шрифтов.
Доклад 3: Анализ и улучшение критического пути рендеринга
- Понятие критического пути рендеринга и его влияние на загрузку.
- Стратегии уменьшения количества критических ресурсов.
- Инструменты для анализа и улучшения производительности.
Доклад 4: Content Delivery Networks (CDN) и их роль в производительности
- Принцип работы CDN и их использование для ускорения загрузки.
- Примеры популярных CDN и интеграция в проекты.
- Как выбрать CDN для оптимизации производительности.
Доклад 5: Минификация файлов CSS и JavaScript
- Принцип минификации и ее влияние на производительность.
- Популярные инструменты для минификации.
- Примеры использования и влияние на размер файлов.