Лучшие практики HTML
Надёжный HTML — это основа любого отличного сайта. Эти советы помогут вам писать семантическую, надёжную и ориентированную на будущее разметку, которую любят браузеры и скринридеры.
Указание явных атрибутов width и height у изображений позволяет браузеру зарезервировать правильное пространство до загрузки изображения, устраняя Cumulative Layout Shift (CLS) — один из Core Web Vitals Google.
<!-- ❌ Плохо: браузер не знает размер до загрузки изображения --> <img src="hero.jpg" alt="Hero image"> <!-- ✅ Хорошо: пространство зарезервировано немедленно --> <img src="hero.jpg" alt="Hero image" width="1200" height="630" loading="lazy">
aspect-ratio: auto в CSS, и браузер автоматически рассчитает правильное соотношение, даже если CSS изменяет размер изображения.Структура документа с правильно вложенными заголовками помогает как поисковым системам понять иерархию контента, так и пользователям скринридеров эффективно перемещаться по странице.
<h1>Заголовок страницы (один на страницу)</h1> <h2>Основной раздел</h2> <h3>Подраздел</h3> <h3>Другой подраздел</h3> <h2>Другой основной раздел</h2>
При открытии ссылок в новой вкладке с target="_blank" открытая страница может получить доступ к вашей странице через window.opener. Добавление rel="noopener noreferrer" предотвращает эту уязвимость безопасности, а также останавливает отправку информации referrer.
<!-- ❌ Уязвимо --> <a href="https://example.com" target="_blank">Перейти</a> <!-- ✅ Безопасно --> <a href="https://example.com" target="_blank" rel="noopener noreferrer">Перейти</a>
Атрибут autocomplete сообщает браузерам и менеджерам паролей, как предзаполнять поля формы. Это значительно улучшает показатели заполнения мобильных форм.
<input type="text" autocomplete="given-name"> <input type="email" autocomplete="email"> <input type="tel" autocomplete="tel"> <input type="password" autocomplete="current-password"> <input type="text" autocomplete="postal-code">
Метатеги Open Graph управляют тем, как ваша страница выглядит при публикации в социальных сетях, таких как Facebook, LinkedIn и Twitter/X. Без них платформы выбирают контент случайным образом.
<meta property="og:title" content="Заголовок страницы"> <meta property="og:description" content="Краткое описание"> <meta property="og:image" content="https://yourdomain.com/og.jpg"> <meta property="og:url" content="https://yourdomain.com/page"> <meta property="og:type" content="website"> <!-- Twitter/X --> <meta name="twitter:card" content="summary_large_image">
Советы и трюки CSS
Современный CSS невероятно мощен. Эти советы охватывают макеты, пользовательские свойства и паттерны, которые экономят ваше время и сохраняют таблицы стилей чистыми.
Хватит бороться с хаками позиционирования. Сокращение place-items CSS Grid центрирует контент по горизонтали и вертикали в две строки — без calc, без трансформаций.
.center-everything { display: grid; place-items: center; } /* Работает и для центрирования на всю страницу */ body { display: grid; place-items: center; min-height: 100vh; }
Пользовательские свойства CSS (переменные) позволяют определить дизайн-токены один раз и переиспользовать их везде. Они также обновляются в реальном времени, делая тёмную тему и темизацию тривиальными.
:root { --color-primary: #6366f1; --color-bg: #ffffff; --radius-md: 12px; --spacing-md: 16px; } @media (prefers-color-scheme: dark) { :root { --color-bg: #0d1117; } } .btn { background: var(--color-primary); border-radius: var(--radius-md); padding: var(--spacing-md); }
clamp(min, preferred, max) позволяет размерам шрифта плавно масштабироваться с шириной viewport — медиазапросы не нужны. Среднее значение обычно является единицей vw.
/* clamp(минимум, предпочтительно, максимум) */ h1 { font-size: clamp(1.8rem, 5vw, 3.5rem); } p { font-size: clamp(0.9rem, 2vw, 1.1rem); line-height: 1.7; max-width: 65ch; /* оптимальная ширина чтения */ }
Включите плавную прокрутку глобально и используйте scroll-margin-top на якорных целях, чтобы фиксированные заголовки не перекрывали контент, к которому прокручивают.
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } } /* Смещение для фиксированного заголовка (например, высотой 70px) */ [id] { scroll-margin-top: 90px; }
scroll-behavior: smooth в медиазапрос prefers-reduced-motion: no-preference — у некоторых пользователей анимированная прокрутка вызывает укачивание.Паттерн auto-fill + minmax() создаёт сетку, которая автоматически регулирует количество колонок в зависимости от доступного пространства — без медиазапросов.
.card-grid { display: grid; grid-template-columns: repeat( auto-fill, minmax(280px, 1fr) ); gap: 24px; } /* 1 колонка на мобильных → 2 → 3 → 4 автоматически */
Никогда не делайте outline: none глобально — это ломает навигацию с клавиатуры. Используйте :focus-visible, чтобы показывать кольца фокуса только для пользователей клавиатуры, сохраняя дизайн чистым для пользователей мыши.
/* ❌ Никогда так не делайте */ * { outline: none; } /* ✅ Скрыть для мыши, показать для клавиатуры */ :focus:not(:focus-visible) { outline: none; } :focus-visible { outline: 2px solid #6366f1; outline-offset: 3px; border-radius: 4px; }
Советы по JavaScript
Пишите более чистый и производительный JavaScript с этими современными паттернами и API браузера.
Вместо прослушивания события scroll (которое срабатывает сотни раз в секунду), используйте API IntersectionObserver, чтобы реагировать только тогда, когда элементы входят в область просмотра или покидают её.
const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); observer.unobserve(entry.target); // прекратить наблюдение } }); }, { rootMargin: '0px 0px -50px 0px' } ); document.querySelectorAll('.animate-on-scroll') .forEach(el => observer.observe(el));
Такие события, как resize и input, срабатывают очень быстро. Debounce откладывает выполнение до тех пор, пока пользователь не прекратит взаимодействие, предотвращая ненужную работу.
function debounce(fn, delay = 300) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; } // Использование window.addEventListener('resize', debounce(() => { recalculateLayout(); }, 200) );
Современный API navigator.clipboard основан на промисах и намного чище, чем старый хак document.execCommand('copy').
async function copyToClipboard(text) { try { await navigator.clipboard.writeText(text); showToast('Скопировано!'); } catch (err) { console.error('Ошибка копирования:', err); } } // Использование document.querySelector('.copy-btn') .addEventListener('click', () => copyToClipboard('Текст для копирования') );
localStorage может выбрасывать ошибки в режиме приватного просмотра или при переполнении хранилища. Всегда оборачивайте его в хелпер с try/catch.
const storage = { get(key, fallback = null) { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : fallback; } catch { return fallback; } }, set(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); return true; } catch { return false; } } }; storage.set('theme', 'dark'); const theme = storage.get('theme', 'light');
Для анимаций, управляемых JavaScript (например, canvas, GSAP), проверяйте настройку пользователя prefers-reduced-motion и соответственно упрощайте или отключайте анимации.
const prefersReduced = window .matchMedia('(prefers-reduced-motion: reduce)') .matches; if (!prefersReduced) { startAnimations(); } else { showStaticFallback(); }
Советы по производительности
Быстрые сайты ранжируются выше, конвертируют лучше и радуют пользователей. Эти советы охватывают самые большие выигрыши в веб-производительности.
Без font-display: swap браузеры скрывают текст до загрузки кастомного шрифта (FOIT). С ним системный шрифт отображается немедленно, затем подменяется — значительно улучшая Largest Contentful Paint (LCP).
<!-- Шаг 1: preconnect --> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <!-- Шаг 2: добавьте &display=swap к URL --> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap" rel="stylesheet">
Изображения WebP и AVIF значительно меньше JPEG и PNG при одинаковом качестве. Используйте элемент <picture>, чтобы предоставлять современные форматы с JPEG-фолбэком для старых браузеров.
<picture> <source srcset="hero.avif" type="image/avif"> <source srcset="hero.webp" type="image/webp"> <img src="hero.jpg" alt="Hero image" width="1200" height="630" loading="lazy"> </picture>
Обычные теги <script> блокируют разбор HTML. defer запускает скрипт после завершения разбора (по порядку). async запускает скрипты сразу после загрузки (порядок не гарантирован).
<!-- Блокирует рендеринг ❌ --> <script src="app.js"></script> <!-- Выполняется по порядку, после DOM ✅ (используйте для большинства скриптов) --> <script src="app.js" defer></script> <!-- Выполняется сразу после готовности ✅ (аналитика, реклама) --> <script src="analytics.js" async></script>
Придерживайтесь анимации transform и opacity — они выполняются в потоке композитора GPU, не вызывая layout или paint. Добавляйте will-change только если профилирование показывает реальную пользу.
/* ❌ Вызывает layout (дорого) */ .bad { transition: width 0.3s, top 0.3s; } /* ✅ Композитинг на GPU (дёшево) */ .good { transition: transform 0.3s, opacity 0.3s; } /* Используйте экономно, только перед сложными анимациями */ .modal-enter { will-change: transform; } .modal-entered { will-change: auto; } /* сбросить после */
Используйте <link rel="preload">, чтобы указать браузеру загружать критически важные ресурсы как можно раньше — до того, как они будут обнаружены в CSS или JS. Идеально для главных изображений, критических шрифтов и ключевых скриптов.
<!-- Предзагрузка главного изображения (элемент LCP) --> <link rel="preload" as="image" href="hero.webp" fetchpriority="high"> <!-- Предзагрузка критического шрифта --> <link rel="preload" as="font" href="font.woff2" type="font/woff2" crossorigin>
Советы по доступности
Доступность — это не чек-лист, это хороший дизайн. Эти советы помогут сделать ваши сайты доступными для всех, включая пользователей скринридеров и тех, кто пользуется клавиатурой.
Кнопки, содержащие только иконку, не имеют видимого текста, который скринридеры могли бы озвучить. Добавьте aria-label, чтобы предоставить описательную метку.
<!-- ❌ Скринридер скажет "кнопка" --> <button><i class="fa-solid fa-times"></i></button> <!-- ✅ Скринридер скажет "Закрыть диалог" --> <button aria-label="Закрыть диалог"> <i class="fa-solid fa-times" aria-hidden="true"></i> </button>
Атрибут lang сообщает скринридерам, какой язык использовать, включает правильную расстановку переносов в CSS и помогает инструментам перевода определить язык контента. Для языков с письмом справа налево также добавьте dir="rtl".
<!-- Русский --> <html lang="ru"> <!-- Арабский (RTL) --> <html lang="ar" dir="rtl"> <!-- Французский --> <html lang="fr">
WCAG 2.1 AA требует коэффициент контрастности не менее 4.5:1 для обычного текста и 3:1 для крупного текста (18px+ жирный или 24px+ обычный). Используйте инструмент проверки контрастности перед развёртыванием.
/* ❌ #6b7280 на #fff = 4.48:1 (не проходит AA для мелкого текста) */ .muted { color: #6b7280; background: #fff; } /* ✅ #4b5563 на #fff = 7.0:1 (проходит AA и AAA) */ .muted { color: #4b5563; background: #fff; }
Иногда вам нужен текст, который могут услышать только скринридеры — например, ссылка «Пропустить до контента» или метка для визуального элемента. Утилитарный класс .sr-only скрывает его визуально, сохраняя доступность.
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; } <!-- Пример ссылки пропуска --> <a href="#main" class="sr-only">Пропустить до содержимого</a>
Когда модальное окно открыто, фокус клавиатуры должен быть ограничен им. В противном случае Tab будет фокусировать элементы за оверлеем, делая модальное окно непригодным для пользователей клавиатуры.
function trapFocus(modal) { const focusable = modal.querySelectorAll( 'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])' ); const first = focusable[0]; const last = focusable[focusable.length - 1]; modal.addEventListener('keydown', e => { if (e.key !== 'Tab') return; if (e.shiftKey ? document.activeElement === first : document.activeElement === last) { e.preventDefault(); (e.shiftKey ? last : first).focus(); } }); first.focus(); }
Советы по адаптивности и мобильным устройствам
Мобильный трафик доминирует. Эти советы гарантируют, что ваши макеты отлично работают на любом размере экрана — от телефона 320px до монитора 4K.
Без метатега viewport мобильные браузеры рендерят страницу с шириной ~980px и затем уменьшают масштаб. Разрешите масштабирование пользователем — его отключение является серьёзным нарушением доступности и ломает масштабирование браузера.
<!-- ❌ Отключает масштабирование пользователем (нарушение доступности) --> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <!-- ✅ Правильно: разрешает масштабирование до 5× --> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5">
Руководства Apple Human Interface Guidelines и WCAG 2.5.5 рекомендуют минимальный размер области касания 44×44 CSS пикселя. Маленькие цели раздражают пользователей и приводят к промахам. Вы можете расширить кликабельную область, не меняя визуальный размер, с помощью padding или псевдоэлементов.
/* Расширение области касания без визуальных изменений */ .icon-btn { position: relative; width: 24px; height: 24px; } .icon-btn::before { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-width: 44px; min-height: 44px; }
Логические свойства CSS, такие как margin-inline-start вместо margin-left, автоматически меняют направление в зависимости от режима письма документа, делая поддержку RTL практически без усилий.
/* Физическое (не переворачивается для RTL) */ .icon { margin-right: 8px; } /* Логическое (автоматически переворачивается в RTL) */ .icon { margin-inline-end: 8px; } /* Другие примеры логических свойств */ .card { padding-inline: 16px; /* слева + справа */ padding-block: 12px; /* сверху + снизу */ border-start-start-radius: 12px; /* верхний левый в LTR */ }
100vh в мобильных браузерах включает в себя интерфейс браузера (адресную строку), вызывая переполнение при её появлении. Новая единица dvh (динамическая высота viewport) подстраивается при показе или скрытии интерфейса браузера.
/* ❌ Переполнение на мобильных при появлении интерфейса браузера */ .hero { height: 100vh; } /* ✅ С фолбэком для старых браузеров */ .hero { height: 100vh; /* фолбэк */ height: 100dvh; /* динамическая высота viewport */ }
svh (малый viewport, всегда исключает интерфейс) и lvh (большой viewport, всегда включает пространство интерфейса).Больше от ResponsiveCheckTool
Примените эти советы на практике
Проверьте, как выглядят ваши адаптивные изменения на мобильных, планшетах и десктопах — мгновенно, бесплатно, без регистрации.
Протестируйте ваш сайт сейчас