Браузер и фронтенд-рантайм
CLS: почему происходят сдвиги лейаута и как их остановить
Статья загружается быстро. Пользователь начинает читать второй абзац. Затем рекламный блок над статьёй заполняется и сдвигает всё вниз на 300 пикселей. Пользователь был на полуслове; теперь он на середине другого абзаца. Он ничего не тапал. Страница сдвинулась сама. Это CLS — и он проще всего предотвращается из трёх vital, если понять одно правило, стоящее за всеми четырьмя классическими причинами.
Как считается оценка.
Каждый раз, когда видимый элемент меняет позицию между двумя кадрами без причины, инициированной пользователем, браузер записывает layout shift. Оценка одного сдвига:
impact fraction × distance fraction
Impact fraction: совокупная площадь всех сдвинувшихся элементов как доля вьюпорта. Если картинка, покрывающая половину вьюпорта, прыгает — impact fraction 0.5. Distance fraction: наибольшее расстояние, на которое сдвинулся любой элемент, как доля высоты вьюпорта. Сдвиг на 20% высоты вьюпорта даёт 0.2.
Таким образом, один сдвиг, двигающий полувьюпортный элемент на 20% высоты вьюпорта, оценивается 0.5 × 0.2 = 0.1 — ровно на «хорошей» границе.
CLS — не пожизненная сумма всех сдвигов. Это сумма сдвигов в худшем session window: кластер сдвигов, где каждый в пределах 1 секунды от предыдущего, и весь кластер охватывает не более 5 секунд.
- Оценка сдвига
- impact fraction × distance fraction
- CLS
- Сумма худшего 5-с session window
- Окно исключения сдвига после ввода
- 500 мс
- Хороший порог (p75)
- ≤0.1
Окно в 500 мс исключения — что CLS не наказывает.
Сдвиги в пределах 500 мс от взаимодействия пользователя исключаются. Открытие аккордеона, раскрытие выпадающего меню, клик «показать ещё» — результирующее движение ожидается пользователем и не засчитывается в CLS. CLS наказывает только движение, которое пользователь не вызвал и не ожидал.
Четыре классические причины и их фиксы.
-
Картинки без размеров.
<img>без атрибутовwidthиheightимеет нулевую высоту до прихода байтов. Когда картинка загружается, браузер узнаёт её размеры, перезапускает компоновку, и всё ниже прыгает вниз. Фикс: всегда задавать атрибутыwidthиheight(современные браузеры автоматически выводятaspect-ratioиз них и резервируют правильно пропорциональный блок). CSS может делать картинку fluid сwidth: 100%; height: auto— атрибуты дают пропорцию, CSS — адаптивный размер. -
Реклама, эмбеды и iframe, инжектируемые в незарезервированное место. Рекламный блок, рендерящий 250 пикселей контента в контейнер без зарезервированной высоты, сдвигает всё ниже. Фикс: оборачивать каждый рекламный и embed-слот в контейнер с
min-height, равным наибольшему ожидаемому размеру. -
Reflow от веб-шрифтов. Фоллбэк-шрифт с другими метриками рендерится первым; когда веб-шрифт загружается, браузер перекомпоновывает текст — символы шире или уже, строки переносятся, элементы ниже сдвигаются. Фикс: использовать
size-adjustи дескрипторыascent-override/descent-overrideшрифта, чтобы метрики фоллбэка совпадали с веб-шрифтом; илиfont-display: optionalдля применения веб-шрифта только при повторных визитах. -
Динамически инжектируемый контент над существующим. Cookie-баннер, панель уведомлений или виджет чата, вставленный над телом страницы в runtime, сдвигает всё вниз. Фикс: вставить в заранее зарезервированное место (контейнер с известной высотой в лейауте), или рендерить как оверлей (fixed/absolute), чтобы не участвовал в flow-лейауте вообще.
Объединяющий принцип: резервировать место для всего, чей конечный размер неизвестен во время парсинга, или убедиться, что оно никогда не участвует в flow-лейауте.
Ловушка с анимациями.
Анимирование layout-свойств — top, left, height, width, margin — создаёт layout shifts и может навредить CLS, даже если движение выглядит преднамеренным для разработчика. Фикс — анимировать transform вместо (translateY, scale), который композитируется и никогда не запускает компоновку. Сдвиг, вызванный CSS-анимацией, всё равно является сдвигом, если он не следует за взаимодействием пользователя в пределах 500 мс.
Почему это работает
Модель session window заменила оригинальную «пожизненную сумму», потому что долго живущие страницы и infinite scroll несправедливо наказывались за сдвиги, происходившие через пять минут после загрузки — далеко за пределами контекста чтения пользователя. Session window фокусирует CLS на всплесках плохого поведения: кластер сдвигов при перезагрузке рекламы или пакет картинок без размеров загружается, а не накопленная стоимость сайта, которым кто-то пользуется час. CLS теперь более репрезентативен для того, что пользователь реально замечает.
Зарезервировать место для поздно загружаемой картинки и устранить CLS
1/3Пользователь открывает аккордеон, и контент под ним сдвигается вниз. Засчитывается ли это в CLS?
Cookie-баннер инжектируется над телом статьи после загрузки страницы, сдвигая весь контент вниз на 60 пикселей. Какой правильный CLS-фикс?
- 01Как считается оценка одного layout shift и что такое CLS session window?
- 02Назовите четыре классические причины CLS и фикс для каждой.
- 03CSS-анимация двигает элемент через 'top' и страница не проходит CLS-аудит. Как починить, не убирая анимацию?
CLS оценивает худший всплеск неожиданного движения лейаута: оценка одного сдвига — impact fraction × distance fraction, и CLS репортит худший 5-секундный session window. Сдвиги в пределах 500 мс от взаимодействия пользователя исключаются — CLS наказывает только движение, которое пользователь не вызвал. Четыре классические причины: картинки без размеров, реклама и эмбеды в контейнерах без зарезервированной высоты, reflow от веб-шрифта при несовпадении метрик фоллбэка, и динамически инжектируемый контент над существующим flow. Фикс для всех четырёх следует одному правилу: всё, чей конечный размер неизвестен во время парсинга, должно иметь зарезервированное место, или использовать overlay-позиционирование вне flow-лейаута. Анимирование layout-свойств (top, left, height) генерирует сдвиги — анимируйте transform вместо.
встречается в143
- Почему GraphQL получает N+1junior
- Механика DataLoader: батчинг на границе тикаmiddle
- Контракты batch-функции: порядок, формы, ошибкиmiddle
- Federation и lookahead: батчинг за пределами DataLoadermiddle
- Защита сложности запросов: depth, cost, persisted queriesmiddle
- Senior GraphQL API: scheduling-контракт, изоляция арендаторов, наблюдаемостьsenior
- Зачем идемпотентность: безопасные retryjunior
- Серверный state machine: четыре состояния idempotency keymiddle
- Outbox и inbox: effectively-once через dual-write границуmiddle
- Конкурентность и архитектура кеша для идемпотентности на масштабеsenior
- Наблюдаемость, production-инциденты и дизайн для глобального масштабаsenior
- Что такое cache stampede и почему он делает всё хужеjunior
- Лок и single-flight: ограничение параллельных rebuildmiddle
- XFetch: вероятностное раннее истечение без координацииmiddle
- Stale-while-revalidate и CDN request coalescingmiddle
- Детектирование stampede и дизайн TTL для продакшенаmiddle
- Метастабильный сбой, fencing-токены и production-постмортемыsenior
- Что такое отношение: таблицы, строки, ключи и ограниченияjunior
- Ограничения, ключи и типы данных Postgresmiddle
- Нормальные формы, денормализация и почему схемы «прилипают»middle
- JSONB, массивы и когда side table побеждаетmiddle
- Heap-хранилище, TOAST и выравнивание колонокsenior
- Целостность схемы: deferral, версионирование и сбои в продакшнеsenior
- Реляционная модель vs документные, wide-column, граф и key-valuesenior
- Index-only scan, Visibility Map и INCLUDEsenior
- Типичные сбои в продакшне и аудит индексовsenior
- pg_statistic, ANALYZE и производственная наблюдаемостьmiddle
- Производственные режимы отказа и стабильность плановsenior
- MVCC: как Postgres раздаёт согласованные снимкиjunior
- Заголовок tuple и механика снимковmiddle
- HOT-обновления и уровни изоляцииmiddle
- VACUUM, bloat и autovacuummiddle
- CLOG, XID wraparound и MultiXactsenior
- SSI и production-тюнинг autovacuumsenior
- Реальные провалы MVCC, deployment-паттерны и распределённые снимкиsenior
- Connection pool: зачем амортизировать стоимость backend Postgresjunior
- Режимы PgBouncer: session, transaction и statementmiddle
- Размер пула: формула (ядра × 2) + шпинделей и двухуровневый стекmiddle
- Исчерпание пула и idle-in-transaction: сценарий отказа в 3 ночиmiddle
- Миграция на transaction mode: план развёртывания и prepared statements в PgBouncer 1.21middle
- Процессная модель Postgres и почему увеличение max_connections снижает производительностьsenior
- Ландшафт пулеров 2026, serverless connection storms и полная таксономия отказовsenior
- Что такое миграция схемы и почему она заменяет ad-hoc DDLjunior
- ADD COLUMN: мгновенно в PG 11+ против перезаписи в старом Postgresjunior
- Режим отказа очереди блокировок: почему мгновенный DDL может заморозить базуmiddle
- Безопасные DDL-паттерны: NOT VALID, CONCURRENTLY и исправления небезопасных операцийmiddle
- Expand-contract: нулевой простой для ломающих изменений схемыmiddle
- Advisory-блокировки, инструменты миграций и координация деплояsenior
- Таксономия сбоев миграций и дисциплина продакшнаsenior
- Зачем нужно шардирование: потолок одного Postgresjunior
- Выбор ключа шарда: стратегии hash, range, list и directorymiddle
- Партиционирование против шардирования: одно слово, два разных понятияmiddle
- Ко-локация и Citus: инвариант, делающий шардирование пригодным к использованиюmiddle
- Режим отказа hot shard: обнаружение, изоляция и долгосрочная политикаmiddle
- Schema-based шардирование и альтернативы мультиарендностиsenior
- Онлайн-решардинг, 2PC и операционная стоимость шардированияsenior
- Семь актов: от CREATE TABLE до Citusjunior
- Акты 1–3 в глубину: схема, индексы и статистика планировщикаmiddle
- Акты 4–6 в глубину: MVCC bloat, connection pooling и безопасные миграцииmiddle
- Акт 7 в глубину: шардинг, co-location и семиуровневый каскад трейдоффовmiddle
- Наблюдаемость, антипаттерны и производственный триажsenior
- Роли Raft, term и почему majority-кворум предотвращает split brainjunior
- Как Raft реплицирует log entry и решает, что его безопасно коммититьmiddle
- Выборы лидера в Raft: таймауты, правила голосования и четыре свойства безопасностиmiddle
- Raft в реальном мире: partition, медленный диск и клиентская маршрутизацияmiddle
- Расширения Raft: pre-vote, learner, snapshot и линеаризуемые чтенияsenior
- Raft в production: membership change, Multi-Raft и observabilitysenior
- Где происходит data fetching — и почему это решает LCPjunior
- Fetch waterfall''''ы — диагностика и лечение через Promise.allmiddle
- React Server Components и Suspense streamingmiddle
- Клиентский кэш: TanStack Query, SWR и stale-while-revalidatemiddle
- LCP, prefetch и race conditions в интерактивном fetchingmiddle
- Senior internals: RSC payload, слои кэша и production паденияsenior
- Трёхстороннее рукопожатие TCPjunior
- Номера последовательности и состояние соединенияmiddle
- DNS: что делает и зачем существуетjunior
- Обход резолвера: перенаправления, типы записей и gluemiddle
- TTL, кеширование и распространение DNSmiddle
- Рукопожатие за 1 RTT: key share и ECDHEmiddle
- Возобновление сессии и 0-RTTmiddle
- WebSocket: HTTP-апгрейд до постоянного соединенияjunior
- Формат WebSocket-фрейма: opcodes, маскирование, фрагментацияmiddle
- Backpressure в WebSocket: когда клиенты не успеваютmiddle
- Реконнект: jittered backoff, thundering herd, восстановление сообщенийsenior
- WebSocket в масштабе: HTTP/2 мультиплексирование, permessage-deflate, C10Msenior
- WebSocket в production: прокси, безопасность и распределённая архитектураsenior
- Что делают обратные проксиjunior
- Health checks, connection draining и slow startmiddle
- Session affinity, consistent hashing и правильное решениеmiddle
- Retry-бури, circuit breakers и load sheddingsenior
- Устойчивая архитектура LB: anycast, zone-aware маршрутизация и observabilitysenior
- Почему QUIC, а не TCP+TLSjunior
- Connection ID и миграция сетиmiddle
- Возобновление 0-RTT и шифрование пакетовsenior
- DDoS: что это и почему работаетjunior
- Атаки усиления и истощение состоянияmiddle
- Ограничение скорости: алгоритмы и архитектураmiddle
- WAF, межсетевые экраны, mTLS и HSTSmiddle
- Отравление DNS-кэша и BGP-перехватsenior
- Эшелонированная защита и экономика атакsenior
- DNS, TCP, TLS по очереди: куда уходят миллисекундыmiddle
- Перехват прокси и шлюзы безопасности: rate limiter, WAF, mTLSmiddle
- Альтернативные пути: QUIC 0-RTT, WebSocket upgrade, миграция соединенияmiddle
- Наблюдаемость: распределённые трейсы, USE/RED и семплированиеsenior
- Устойчивость: каскадные повторы, circuit breakers и error budgetsenior
- Что такое три сигнала: метрики, логи, трейсыjunior
- Зачем нужны структурные логи: дневник против таблицыjunior
- Схема продакшн-лога: поля, которые несёт каждая строкаmiddle
- PII-редакция и log injectionsenior
- OTel Logs Data Model и audit-логи как подсистемаsenior
- SLI, SLO и error budget: надёжность в числахjunior
- Error budget policy, latency SLO и составные journeysmiddle
- Продакшн-отказы SLO, самонаблюдаемость, безопасность и общая картинаsenior
- Петля инцидента: от пейджера до постмортема до предотвращенияmiddle
- Cache lines и false sharing: когда параллелизм замедляет кодmiddle
- SIMD и data layout: AoS vs SoA и разница в 4–8xmiddle
- Cache-oblivious алгоритмы, PGO и production failuressenior
- GC в production: наблюдаемость, безопасность, edge cases и управление флотомsenior
- Batching: амортизируй фиксированную цену каждой операцииjunior
- Окно батчинга: размер и время ожиданияmiddle
- Batching в Kafka и Postgresmiddle
- io_uring и наблюдаемость пакетированияmiddle
- От Nagle до io_uring: эволюция пакетированияmiddle
- Backpressure, изоляция сбоев и безопасность батчей в продакшенеsenior
- CI enforcement и RUM: делаем бюджеты рабочимиmiddle
- V8 JIT-пайплайн, HTTP-приоритеты и безопасность bundlesenior
- Цикл performance: дисциплина, а не проектjunior
- Классификация и исправление: сопоставление family bottleneck с методамиmiddle
- Observability-стек и CI gates: ловить регрессии до выпускаmiddle
- От инцидента к enforcement: SLO burn до верифицированного исправления за 35 минутmiddle
- Культура, экономика и масштаб performancesenior
- At-most-once, at-least-once, exactly-once: три контракта доставкиjunior
- Три ножки сбоя — где реально происходят дубликаты и потериmiddle
- Consumer-side dedup: самый дешёвый путь к exactly-once processingmiddle
- Kafka exactly-once semantics: idempotent producer и транзакцииmiddle
- SQS visibility timeout, DLQ и outbox patternmiddle
- Exactly-once в production: impossibility-доказательство, гибридные паттерны и реальные инцидентыsenior
- Что такое OAuth и почему пароли — не ответjunior
- Authorization code flow с PKCEmiddle
- Валидация ID-токена и управление JWKS-кешемmiddle
- Ротация refresh-токенов и scope-based least privilegemiddle
- Sender-constrained токены: DPoP и mTLSsenior
- OAuth в production: audience атаки, observability и реальные провалыsenior