CSS справочник для собеседования

📖 Это объёмная статья-справочник.
Читайте последовательно или используйте как шпаргалку.
Совет автора: скопируйте ссылку на эту статью и вставьте в ChatGPT, Claude или любую другую нейросеть — сайт отлично индексируется. Спрашивайте ИИ по ходу изучения, если что-то непонятно.
Содержание
- Ключевые моменты для запоминания
- Быстрая справка по псевдо-селекторам
- Когда что использовать
- Блочная модель и позиционирование
- 🎯 Введение
- Box-sizing: ключевое свойство
- Позиционирование элементов
- Псевдоклассы и псевдоэлементы
- Оптимизация производительности
- Transition: плавные переходы
- Animation: сложные анимации
- Определение видимости элемента
Ключевые моменты для запоминания
Блочная модель
- ✅ Используйте
box-sizing: border-boxглобально - ✅ Помните о margin collapsing для вертикальных отступов
- ✅ Padding и border увеличивают размер при
content-box
Позиционирование
- ✅
absolute/fixed+transformдля перемещаемых элементов - ✅
stickyдля липких заголовков внутри секций - ✅ Помните о
transformу родителя — он ломаетfixed
Производительность
- ✅ Анимируйте только
transformиopacity - ❌ Избегайте анимации
width,height,top,left - ✅ Используйте
will-changeвременно и точечно - ✅ Используйте
@media (prefers-reduced-motion)для доступности
Анимации
- ✅
transitionдля простых состояний (hover, focus) - ✅
animationдля сложных сценариев с таймлайном - ✅ WAAPI для программного контроля
- ✅ 150-200ms для UI, < 500ms для крупных изменений
Видимость элементов
- ✅
IntersectionObserver— основной инструмент - ⚠️
getBoundingClientRect— только для разовых проверок - ❌ Scroll + offsets — устаревший подход
Быстрая справка по псевдо-селекторам
Псевдоклассы (состояния):
:hover:focus:active:checked:disabled:valid:invalid
Псевдоклассы (структура):
:first-child:last-child:nth-child():only-child:not():has()
Псевдоэлементы:
::before::after::first-letter::first-line::selection::placeholder
Когда что использовать
| Задача | Решение |
|---|---|
| Hover-эффект | transition |
| Лоадер | animation + @keyframes |
| Drag & drop | position: absolute + transform |
| Модальное окно | position: fixed |
| Липкий заголовок | position: sticky |
| Lazy loading | IntersectionObserver |
| Анимация при скролле | IntersectionObserver + CSS |
Блочная модель и позиционирование
Полное руководство по блочной модели CSS, позиционированию элементов, анимациям и оптимизации производительности.
🎯 Введение
Эта шпаргалка охватывает фундаментальные концепции CSS, которые необходимы для создания современных веб-интерфейсов. Материал структурирован от базовых понятий к продвинутым техникам оптимизации.

Что такое блочная модель?
Блочная модель — это схема, по которой браузер рассчитывает размер и расположение каждого элемента на странице. Каждый элемент представляется как прямоугольник из 4 слоёв.
Четыре слоя блочной модели
1️⃣ Content (Содержимое)
Содержимое элемента: текст, картинка, вложенные блоки
- Управляется свойствами:
width,height - Это база, от которой всё считается
Пример:
.box {
width: 200px;
height: 100px;
}
2️⃣ Padding (Внутренний отступ)
Внутренний отступ между контентом и границей
- Свойства:
padding,padding-top/right/bottom/left
Padding увеличивает фактический размер элемента при использовании:
box-sizing: content-box
Пример:
.box {
padding: 20px; /* со всех сторон */
/* или */
padding: 10px 20px 10px 20px; /* top right bottom left */
}
3️⃣ Border (Граница)
Граница вокруг padding + content
- Свойства:
border,border-width/style/color - Также входит в общий размер элемента
Пример:
.box {
border: 2px solid #333;
/* или детально */
border-width: 2px;
border-style: solid;
border-color: #333;
}
4️⃣ Margin (Внешний отступ)
Внешний отступ от других элементов
- Свойства:
margin,margin-top/right/bottom/left
Margin НЕ входит в размер элемента, но влияет на расположение
.box {
margin: 20px auto; /* 20px сверху/снизу, центрирование по горизонтали */
}
Box-sizing: ключевое свойство
Два режима расчёта размера
content-box (по умолчанию)
box-sizing: content-box; /* значение по умолчанию */
widthиheightзадают только размер контентаpaddingиborderприбавляются сверху- Итоговая формула:
total = width + padding + border
Пример:
.box {
box-sizing: content-box;
width: 200px;
padding: 20px;
border: 5px solid black;
}
/* Итоговая ширина = 200 + (20*2) + (5*2) = 250px */
border-box (стандарт де-факто)
box-sizing: border-box;
widthиheightвключают content + padding + border- Итоговая формула:
total = width - Гораздо удобнее для layoutов!
Используйте
border-boxглобально для всех элементов — это упрощает расчёт размеров
Пример глобального применения:
*, *::before, *::after {
box-sizing: border-box;
}
Пример с border-box:
.box {
box-sizing: border-box;
width: 200px;
padding: 20px;
border: 5px solid black;
}
/* Итоговая ширина = 200px (padding и border внутри) */
Визуализация в DevTools
При наведении на элемент в браузере:
- border-box: вся подсветка показывает один размер блока
- content-box: padding и border выпирают за пределы заданного размера
Что это такое?
Margin collapsing — это поведение вертикальных margin'ов, когда итоговый отступ равен максимальному значению, а не сумме.
Пример схлопывания
.block-1 {
margin-bottom: 10px;
}
.block-2 {
margin-top: 20px;
}
/* Итоговый отступ между ними = 20px (не 30px!) */
Берётся максимальный margin, это и есть margin collapsing
Берётся максимальный margin, это и есть margin collapsing
Когда margin'ы схлопываются?
✅ Схлопываются при соблюдении всех условий:
- Вертикальные margin'ы (
margin-top/margin-bottom) - Обычный поток документа (
display: block) - Нет
border,paddingмежду ними - Элементы не используют
inline,flex,grid,absolute,fixed - Родитель не создаёт новый контекст форматирования
Типовые случаи схлопывания:
- Соседние блоки в потоке
- Родитель ↔ первый/последний ребёнок
- Пустой блок (без content, padding, border)
Когда НЕ схлопываются?
❌ Не схлопываются:
- Горизонтальные margin'ы (
margin-left/right) — никогда - Элемент является flex или grid item
- Есть
paddingилиborderмежду элементами position: absoluteилиposition: fixed- Есть
overflow: hidden,autoилиscroll - Используется
display: flow-root
Чтобы предотвратить схлопывание, добавьте родителю
overflow:hiddenилиdisplay:flow-root
Пример предотвращения:
.parent {
display: flow-root; /* создаёт новый контекст форматирования */
}
Позиционирование элементов
Типы позиционирования
CSS предоставляет 5 типов позиционирования элементов. Выбор правильного типа критически важен для производительности.
Сравнительная таблица
| Свойство | В потоке? | К чему привязан | Скролл | Применение |
|---|---|---|---|---|
static | ✅ Да | Своё место | Едет | Обычные элементы |
relative | ✅ Да | Своя позиция | Едет | Небольшие сдвиги |
sticky | ✅ Да | Родитель + offset | Прилипает | Липкие заголовки |
absolute | ❌ Нет | Контейнер | Едет | Dropdown, tooltips |
fixed | ❌ Нет | Viewport | Не едет | Модалки, навигация |
1️⃣ position: static (по умолчанию)
position: static; /* значение по умолчанию */
- Элемент находится в нормальном потоке документа
- Свойства
top,left,right,bottomне работают - Любое движение вызывает reflow всего DOM
Не используйте
staticдля перемещаемых элементов — это вызывает полный пересчёт layout
2️⃣ position: relative
.box {
position: relative;
top: 10px;
left: 20px;
}
- Элемент остаётся в потоке, но смещается относительно своей позиции
- Место в потоке сохраняется (как будто элемент не двигался)
- Создаёт контекст позиционирования для absolute-потомков
Применение:
- Небольшие визуальные сдвиги
- Создание контейнера для absolute-элементов
3️⃣ position: absolute
.box {
position: absolute;
top: 50px;
left: 100px;
}
- Элемент вынут из нормального потока
- Позиционируется относительно ближайшего positioned-родителя (с
position!=static) - Если нет такого родителя — относительно
<body> - При скролле страницы элемент двигается вместе с ней
Применение:
- Выпадающие меню
- Tooltips внутри контейнера
- Локальные overlay-окна
Если двигать через
top/left— будетreflow.Для плавного движения используйте
transform!
4️⃣ position: fixed
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
- Элемент вынут из потока
- Позиционируется относительно viewport (окна браузера)
- При скролле НЕ двигается, всегда на месте
- Всегда поверх остального контента
Применение:
- Модальные окна
- Фиксированная навигация
- Глобальные уведомления
Если у родителя есть
transform,filterилиperspective—fixedперестаёт быть фиксированным и ведёт себя какabsolute!
Пример проблемы:
.app {
transform: translateZ(0); /* создаёт новый containing block */
}
.modal {
position: fixed; /* теперь привязан к .app, а не к viewport! */
}
5️⃣ position: sticky
.header {
position: sticky;
top: 0;
}
- Гибрид
relative+fixed - Пока внутри контейнера — ведёт себя как
relative - Достиг порога (
top/bottom/left/right) — становится какfixed - Но только в пределах родительского контейнера
Обязательные условия:
- Должен быть указан offset (
top,bottom, etc.) - Работает только в scroll-контейнере
- Родитель не должен иметь
overflow: hidden
Применение:
- Sticky-заголовки таблиц
- Липкие сайдбары
- Фиксирующиеся секции при скролле
Stickyотлично подходит для навигации внутри секций, но не для глобальных модальных окон
Псевдоклассы и псевдоэлементы
В чём разница?
- Псевдоклассы (
:) — описывают состояние или положение элемента - Псевдоэлементы (
::) — создают виртуальные части элемента, которых нет в DOM
Старый синтаксис
:beforeдопустим, но правильный —::before
Псевдоклассы — состояния и положение
Состояния элемента
/* Интерактивные состояния */
a:hover { color: blue; } /* наведение мыши */
button:active { transform: scale(0.95); } /* момент клика */
input:focus { border-color: blue; } /* фокус */
input:focus-visible { outline: 2px solid blue; } /* фокус с клавиатуры */
/* Состояния ссылок */
a:visited { color: purple; } /* посещённая ссылка */
a:link { color: blue; } /* не посещённая */
/* Состояния форм */
input:disabled { opacity: 0.5; } /* отключено */
input:enabled { cursor: text; } /* включено */
input:checked { background: green; } /* выбрано (checkbox/radio) */
input:valid { border-color: green; } /* валидное значение */
input:invalid { border-color: red; } /* невалидное */
input:required { border-left: 3px solid red; } /* обязательное */
Структурные псевдоклассы
/* Позиция среди всех детей */
li:first-child { font-weight: bold; } /* первый ребёнок */
li:last-child { margin-bottom: 0; } /* последний */
li:nth-child(2) { color: red; } /* второй ребёнок */
li:nth-child(odd) { background: #f0f0f0; } /* нечётные: 1, 3, 5... */
li:nth-child(even) { background: white; } /* чётные: 2, 4, 6... */
li:nth-child(3n) { color: blue; } /* каждый третий */
/* Позиция среди элементов своего типа */
p:first-of-type { margin-top: 0; } /* первый <p> */
p:last-of-type { margin-bottom: 0; } /* последний <p> */
p:nth-of-type(2) { font-size: 18px; } /* второй <p> */
/* Особые случаи */
div:only-child { text-align: center; } /* единственный ребёнок */
p:only-of-type { color: red; } /* единственный <p> */
:nth-childсчитает ВСЕ элементы, а не только нужного типа. Используйте:nth-of-typeдля конкретных тегов.
Логические псевдоклассы (современные)
/* Исключение */
li:not(.active) { opacity: 0.6; } /* все li кроме .active */
/* Группировка */
:is(h1, h2, h3) { margin-top: 1em; } /* любой из заголовков */
/* Группировка с нулевой специфичностью */
:where(h1, h2, h3) { margin-top: 1em; } /* не увеличивает вес селектора */
/* Родитель по наличию потомка (революция!) */
.card:has(img) { padding: 0; } /* карточка, содержащая картинку */
form:has(:invalid) { border: 2px solid red; } /* форма с невалидным полем */
/* Корень и пустота */
:root { --main-color: blue; } /* корень документа (html) */
div:empty { display: none; } /* пустой элемент без контента */
:has()— это "родительский селектор", о котором мечтали годами. Но используйте аккуратно — он дорогой в плане производительности.
Псевдоэлементы — виртуальные части
Основные псевдоэлементы
/* Добавление контента */
.icon::before {
content: '★ '; /* обязательное свойство! */
color: gold;
}
.link::after {
content: ' →';
opacity: 0;
transition: opacity 0.2s;
}
.link:hover::after {
opacity: 1;
}
/* Стилизация текста */
p::first-letter {
font-size: 2em; /* буквица */
float: left;
line-height: 1;
}
p::first-line {
font-weight: bold; /* первая строка */
color: #333;
}
/* Выделение текста */
::selection {
background: yellow; /* цвет выделения */
color: black;
}
/* Плейсхолдер */
input::placeholder {
color: #999;
font-style: italic;
}
/* Маркеры списков */
li::marker {
color: blue;
font-weight: bold;
}
/* Backdrop для модальных окон */
dialog::backdrop {
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(5px);
}
::beforeи::afterне работают без свойстваcontent!Даже
content: ''обязателен.
Пример правильного использования:
/* Декоративный элемент */
.button::before {
content: ''; /* пустое, но обязательное! */
position: absolute;
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, blue, transparent);
bottom: 0;
left: 0;
}
Оптимизация производительности
Rendering Pipeline браузера
Когда изменяется CSS-свойство, браузер проходит через несколько этапов:
JavaScript → Style → Layout → Paint → Composite
Этапы рендеринга:
- JavaScript — изменение DOM/стилей
- Style — пересчёт CSS
- Layout (Reflow) — расчёт геометрии элементов
- Paint (Repaint) — отрисовка пикселей
- Composite — сборка слоёв на GPU
⚠️ Чем раньше этап — тем дороже операция!
Типы CSS-свойств по производительности
🟢 Безопасные свойства (только Composite)
/* Анимируются на GPU без reflow/repaint */ transform: translateX(100px); /* ✅ */ transform: scale(1.2); /* ✅ */ transform: rotate(45deg); /* ✅ */ opacity: 0.5; /* ✅ */
✅ Золотое правило. Для анимаций используйте ТОЛЬКО
transformиopacity
🟡 Средние (Layout + Paint + Composite)
/* Вызывают repaint, но не reflow */ color: red; /* ⚠️ */ background-color: blue; /* ⚠️ */ box-shadow: 0 2px 5px; /* ⚠️ дорогое! */
🔴 Опасные (полный цикл)
/* Вызывают reflow — пересчёт всего layout */ width: 200px; /* ❌ */ height: 100px; /* ❌ */ top: 50px; /* ❌ */ left: 100px; /* ❌ */ margin: 20px; /* ❌ */ padding: 10px; /* ❌ */ border: 1px; /* ❌ */
❌ Критическая ошибка! Анимация
width,height,top,leftубьёт производительность!
Правильная техника drag & drop
❌ Неправильно (вызывает reflow)
// Плохо — изменяем layout-свойства
element.style.left = `${x}px`;
element.style.top = `${y}px`;
✅ Правильно (только composite)
.draggable {
position: absolute; /* вынут из потока */
will-change: transform; /* подсказка браузеру */
}
// Отлично — GPU-ускорение, без reflow
element.style.transform = `translate(${x}px, ${y}px)`;
Сравнение способов перемещения
| Способ | Reflow | Repaint | GPU | Скорость |
|---|---|---|---|---|
static + изменение layout | ❌ Да | ❌ Да | ❌ Нет | 🐌 Очень медленно |
absolute + top/left | ⚠️ Частично | ⚠️ Да | ❌ Нет | 🐢 Медленно |
absolute + transform | ✅ Нет | ✅ Нет | ✅ Да | 🚀 Быстро |
fixed + transform | ✅ Нет | ✅ Нет | ✅ Да | 🚀 Быстро |
will-change: подсказка браузеру
.animated-element {
/* Говорим браузеру, что будем менять transform */
will-change: transform;
}
⚠️ Используйте
will-changeтолько для элементов, которые реально будут анимироваться. НЕ применяйте глобально — это создаст слишком много слоёв. Убирайте после завершения анимации.
Правильное использование:
// Добавляем перед анимацией
element.style.willChange = 'transform';
// Анимация...
// Убираем после завершения
element.addEventListener('transitionend', () => {
element.style.willChange = '';
});
Transition: плавные переходы
Что такое transition?
Transition позволяет плавно анимировать изменение CSS-свойств вместо мгновенного переключения.
Базовый синтаксис
transition: property duration timing-function delay;
Компоненты:
- property — что анимировать (или
all) - duration — длительность (ms или s)
- timing-function — функция замедления
- delay — задержка перед стартом (опционально)
Примеры использования
Простой переход
.button {
background-color: blue;
transition: background-color 200ms ease;
}
.button:hover {
background-color: darkblue;
}
Множественные свойства
.card {
transform: scale(1);
opacity: 1;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition:
transform 300ms cubic-bezier(0.34, 1.56, 0.64, 1),
opacity 200ms ease-out,
box-shadow 300ms ease;
}
.card:hover {
transform: scale(1.05);
opacity: 0.9;
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
}
Использование all (осторожно!)
.element {
transition: all 200ms ease;
}
⚠️
allудобно, но может анимировать ненужные свойства. Лучше указывать конкретные.
Timing functions (функции замедления)
/* Предустановленные */ transition: transform 300ms linear; /* без замедления */ transition: transform 300ms ease; /* стандартное */ transition: transform 300ms ease-in; /* ускорение в начале */ transition: transform 300ms ease-out; /* замедление в конце */ transition: transform 300ms ease-in-out; /* оба */ /* Кастомная кривая Безье */ transition: transform 300ms cubic-bezier(0.68, -0.55, 0.265, 1.55); /* Ступенчатая анимация */ transition: transform 300ms steps(5);
✅ Используйте
ease-outдля большинства интерактивных элементов — это естественнее
Что можно безопасно анимировать?
✅ Безопасно (хорошая производительность)
transform /* ✅ GPU */ opacity /* ✅ GPU */ filter /* ✅ GPU (осторожно с blur) */
⚠️ Осторожно (средняя производительность)
color background-color border-color box-shadow /* может быть тяжёлым */
❌ Избегать (плохая производительность)
width, height top, left, right, bottom margin, padding border-width
Рекомендации по времени
/* Микро-взаимодействия */
.button { transition: transform 150ms; } /* 120-200ms */
/* UI элементы */
.dropdown { transition: opacity 200ms; } /* 200-300ms */
/* Крупные изменения */
.modal { transition: transform 400ms; } /* 300-500ms */
💡 UX правило: быстрые анимации (< 200ms) ощущаются отзывчивыми. Медленные (> 500ms) раздражают.
Animation: сложные анимации
Отличие от transition
| Свойство | Transition | Animation |
|---|---|---|
| Триггер | Изменение CSS | Автоматический |
| Контроль | A → B | Полный таймлайн |
| Циклы | Нет | Есть |
| Паузы | Нет | Есть |
| Шаги | Нет | Keyframes |
Базовый синтаксис
Определение keyframes
@keyframes slideIn {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* Или с процентами */
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
Применение анимации
.element {
animation: slideIn 500ms ease-out;
}
/* Полный синтаксис */
.element {
animation-name: slideIn;
animation-duration: 500ms;
animation-timing-function: ease-out;
animation-delay: 100ms;
animation-iteration-count: infinite;
animation-direction: alternate;
animation-fill-mode: forwards;
animation-play-state: running;
}
Свойства animation
animation-iteration-count
animation-iteration-count: 1; /* один раз */ animation-iteration-count: 3; /* три раза */ animation-iteration-count: infinite; /* бесконечно */
animation-direction
animation-direction: normal; /* 0% → 100% */ animation-direction: reverse; /* 100% → 0% */ animation-direction: alternate; /* туда-сюда */ animation-direction: alternate-reverse; /* обратно туда-сюда */
animation-fill-mode
animation-fill-mode: none; /* не сохраняет стили */ animation-fill-mode: forwards; /* остаётся на 100% */ animation-fill-mode: backwards; /* применяет 0% до старта */ animation-fill-mode: both; /* оба */
animation-play-state
.element {
animation: spin 2s linear infinite;
}
.element:hover {
animation-play-state: paused; /* пауза на hover */
}
Практические примеры
Спиннер загрузки
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.loader {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
Пульсация
@keyframes pulse {
0%, 100% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.05);
opacity: 0.8;
}
}
.notification {
animation: pulse 2s ease-in-out infinite;
}
Появление с эффектом
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: fadeInUp 600ms ease-out forwards;
}
/* С задержкой для каждого элемента */
.card:nth-child(1) { animation-delay: 0ms; }
.card:nth-child(2) { animation-delay: 100ms; }
.card:nth-child(3) { animation-delay: 200ms; }
Печатная машинка
@keyframes typing {
from { width: 0; }
to { width: 100%; }
}
@keyframes blink {
50% { border-color: transparent; }
}
.typewriter {
width: 0;
overflow: hidden;
white-space: nowrap;
border-right: 2px solid;
animation:
typing 3s steps(30) forwards,
blink 0.5s step-end infinite;
}
Web Animations API (JavaScript)
Современный способ управления анимациями через JavaScript:
const element = document.querySelector('.box');
// Создание анимации
const animation = element.animate(
[
{ transform: 'scale(1)', opacity: 1 },
{ transform: 'scale(1.2)', opacity: 0.5 },
{ transform: 'scale(1)', opacity: 1 }
],
{
duration: 1000,
iterations: Infinity,
easing: 'ease-in-out'
}
);
// Управление
animation.pause();
animation.play();
animation.reverse();
animation.cancel();
// События
animation.onfinish = () => console.log('Завершено');
🔥 Преимущества WAAPI
- ✅ Полный контроль через JavaScript
- ✅ Хорошая производительность
- ✅ Не нужно писать
@keyframes
Доступность анимаций
/* Отключение анимаций для пользователей с настройкой */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
✅ Всегда уважайте настройку
prefers-reduced-motion— для некоторых людей анимации вызывают дискомфорт
Определение видимости элемента
IntersectionObserver API (современный подход)
IntersectionObserver — это нативный API браузера для определения, находится ли элемент в области видимости (viewport).
Преимущества
- ✅ Асинхронная работа (не блокирует main thread)
- ✅ Без reflow
- ✅ Отличная производительность
- ✅ Браузер сам оптимизирует проверки
Базовое использование
// Создание Observer
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Элемент появился в viewport ✅
console.log('Элемент виден!');
entry.target.classList.add('visible');
} else {
// Элемент вышел из viewport
entry.target.classList.remove('visible');
}
});
});
// Наблюдение за элементом
const element = document.querySelector('.watch-me');
observer.observe(element);
// Остановка наблюдения
// observer.unobserve(element);
// observer.disconnect(); // остановить всё
Настройки (options)
const observer = new IntersectionObserver(
(entries) => { /* callback */ },
{
root: null, // null = viewport, или конкретный элемент
rootMargin: '0px', // отступы (как CSS margin)
threshold: 0.5 // 0-1, какая часть должна быть видна
}
);
Примеры с разными threshold
// Срабатывает, когда хоть 1px виден
const observer1 = new IntersectionObserver(callback, {
threshold: 0
});
// Срабатывает, когда 50% элемента видно
const observer2 = new IntersectionObserver(callback, {
threshold: 0.5
});
// Срабатывает только когда элемент полностью виден
const observer3 = new IntersectionObserver(callback, {
threshold: 1.0
});
// Несколько порогов
const observer4 = new IntersectionObserver(callback, {
threshold: [0, 0.25, 0.5, 0.75, 1.0]
});
Практические применения
Lazy loading изображений
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // загружаем реальное изображение
img.classList.add('loaded');
imageObserver.unobserve(img); // больше не наблюдаем
}
});
});
// HTML: <img data-src="real-image.jpg" src="placeholder.jpg">
document.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img);
});
Анимация при появлении
const animateObserver = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate-in');
animateObserver.unobserve(entry.target);
}
});
},
{ threshold: 0.2 }
);
document.querySelectorAll('.animate-on-scroll').forEach(el => {
animateObserver.observe(el);
});
.animate-on-scroll {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s, transform 0.6s;
}
.animate-on-scroll.animate-in {
opacity: 1;
transform: translateY(0);
}
Отслеживание просмотров (аналитика)
const viewTracker = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// Элемент виден минимум 50% в течение времени
const elementId = entry.target.dataset.trackId;
analytics.track('element_viewed', { id: elementId });
viewTracker.unobserve(entry.target);
}
});
},
{ threshold: 0.5 }
);
Альтернативный метод: getBoundingClientRect
Синхронная проверка координат элемента. Подходит для разовых проверок.
function isInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= window.innerHeight &&
rect.right <= window.innerWidth
);
}
// Частичная видимость
function isPartiallyVisible(element) {
const rect = element.getBoundingClientRect();
return (
rect.top < window.innerHeight &&
rect.bottom > 0
);
}
// Использование
const box = document.querySelector('.box');
if (isInViewport(box)) {
console.log('Элемент полностью виден');
}
⚠️
getBoundingClientRect()вызывает layout read, что может привести к reflow. Не используйте в scroll-обработчиках без debounce/throttle!
Когда использовать getBoundingClientRect
✅ Разовая проверка
✅ Нужны точные координаты
✅ Простая логика без частых вызовов
❌ Не использовать:
- В scroll-обработчиках
- Для множества элементов
- Когда нужна оптимальная производительность
Сравнение методов
| Метод | Асинхронность | Reflow | Производительность | Применение |
|---|---|---|---|---|
| IntersectionObserver | ✅ Да | ❌ Нет | 🚀 Отличная | Lazy load, анимации, аналитика |
| getBoundingClientRect | ❌ Нет | ⚠️ Да | 🐢 Средняя | Разовые проверки |
| scroll + offsets | ❌ Нет | ⚠️ Да | 🐌 Плохая | Устарело, не использовать |
✅ Всегда используйте
IntersectionObserverдля проверки видимости — это современный и производительный способ
Итоги
Этот справочник концентрируется на темах CSS, которые чаще всего проверяют на собеседованиях: блочная модель, позиционирование, псевдоселекторы, производительность, анимации и работа с видимостью элементов.
Ключевые выводы:
- Всегда использовать
box-sizing: border-box, понимать влияние padding, border и margin, а также поведение margin collapsing. - Осознанно выбирать тип позиционирования:
relative— для контекста,absolute— локальные элементы,fixed— глобальные интерфейсы,sticky— липкие секции. - Чётко различать псевдоклассы (состояния) и псевдоэлементы (виртуальные части элемента), понимать современные селекторы
:is,:where,:has. - Для производительности анимировать только
transformиopacity, избегать свойств, вызывающих reflow (width,height,top,left). - Использовать
transitionдля простых взаимодействий,animation— для сложных сценариев, WAAPI — когда нужен программный контроль. - Учитывать pipeline браузера: Style → Layout → Paint → Composite, где изменения layout наиболее дорогие.
- Для отслеживания появления элементов применять
IntersectionObserver, избегая устаревших scroll-хаков. - Добавлять
prefers-reduced-motionдля доступности и снижения нагрузки.
Главная идея статьи — писать CSS не только правильно визуально, но и с пониманием внутренней работы браузера и стоимости изменений для рендеринга.