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

📖 Это объёмная статья-справочник.

Читайте последовательно или используйте как шпаргалку.

Совет автора: скопируйте ссылку на эту статью и вставьте в ChatGPT, Claude или любую другую нейросеть — сайт отлично индексируется. Спрашивайте ИИ по ходу изучения, если что-то непонятно.

Содержание

Ключевые моменты для запоминания

Блочная модель

  • ✅ Используйте 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 & dropposition: absolute + transform
Модальное окноposition: fixed
Липкий заголовокposition: sticky
Lazy loadingIntersectionObserver
Анимация при скролле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
  • Родитель не создаёт новый контекст форматирования

Типовые случаи схлопывания:

  1. Соседние блоки в потоке
  2. Родитель ↔ первый/последний ребёнок
  3. Пустой блок (без 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 или perspectivefixed перестаёт быть фиксированным и ведёт себя как 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
  • Но только в пределах родительского контейнера

Обязательные условия:

  1. Должен быть указан offset (top, bottom, etc.)
  2. Работает только в scroll-контейнере
  3. Родитель не должен иметь 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

Этапы рендеринга:

  1. JavaScript — изменение DOM/стилей
  2. Style — пересчёт CSS
  3. Layout (Reflow) — расчёт геометрии элементов
  4. Paint (Repaint) — отрисовка пикселей
  5. 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)`;

Сравнение способов перемещения

СпособReflowRepaintGPUСкорость
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;

Компоненты:

  1. property — что анимировать (или all)
  2. duration — длительность (ms или s)
  3. timing-function — функция замедления
  4. 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

СвойствоTransitionAnimation
ТриггерИзменение 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 (widthheighttopleft).
  • Использовать transition для простых взаимодействий, animation — для сложных сценариев, WAAPI — когда нужен программный контроль.
  • Учитывать pipeline браузера: Style → Layout → Paint → Composite, где изменения layout наиболее дорогие.
  • Для отслеживания появления элементов применять IntersectionObserver, избегая устаревших scroll-хаков.
  • Добавлять prefers-reduced-motion для доступности и снижения нагрузки.

Главная идея статьи — писать CSS не только правильно визуально, но и с пониманием внутренней работы браузера и стоимости изменений для рендеринга.

Написать комментарий