Отладка сдвигов макета (layout shifts)

В первой части этой статьи обсуждаются инструменты для отладки сдвигов макета, а во второй части обсуждаются алгоритмы выявления причин сдвига макета.

Инструменты

Layout Instability API

Layout Instability API — это механизм браузера для измерения сдвигов макета и создания отчетов. Все инструменты для отладки сдвигов макета, включая DevTools, построены на использовании этого API. Однако прямое использование Layout Instability API является мощным инструментом отладки из-за его гибкости.

Layout Instability API поддерживается преимущественно Chromium-браузерами.

Использование

Фрагмент кода, который измеряет Cumulative Layout Shift (CLS, накопительный сдвиг макета), может служить и для отладки сдвигов макета.
CLS — важный, ориентированный на пользователя показатель для измерения визуальной стабильности. Он помогает количественно оценить, как часто пользователи сталкиваются с неожиданными сдвигами макета. Чем ниже уровень CLS, тем — лучше.

Приведенный ниже фрагмент записывает информацию о сдвиге макета в консоль. Просмотр этого журнала предоставит вам информацию о том, когда, где и как произошел сдвиг макета.

let cls = 0;
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (!entry.hadRecentInput) {
      cls += entry.value;
      console.log('Current CLS value:', cls, entry);
    }
  }
}).observe({type: 'layout-shift', buffered: true});

При запуске этого скрипта имейте в виду, что:

  • Параметр buffered: true указывает, что PerformanceObserver должен проверять буфер записей производительности (performance entry buffer) браузера на предмет записей производительности, которые были созданы до инициализации наблюдателя. Поэтому PerformanceObserver будет сообщать о сдвигах макета, которые произошли как до, так и после его инициализации. Об этом надо помнить при просмотре журналов консоли. Первоначальный избыток сдвигов макета может говорить об отставании в формировании отчетов, а не о внезапном возникновении большого количества сдвигов макета.
  • Чтобы не повлиять на производительность, PerformanceObserver ждет, когда освободится основной поток, чтобы сообщить о сдвигах макета. В результате, в зависимости от того, насколько занят основной поток, может быть небольшая задержка между тем, когда происходит сдвиг макета, и когда он регистрируется в консоли.
  • Этот сценарий игнорирует сдвиги макета, произошедшие в течение 500мс после действий пользователя, и поэтому не учитываются в CLS.

Информация о сдвигах макета передается с помощью комбинации двух API: LayoutShift и LayoutShiftAttribution. Далее каждый из этих интерфейсов будет рассматриваться подробнее.

LayoutShift

О каждом сдвиге макета сообщается с помощью интерфейса LayoutShift. Содержимое самой записи выглядит так:

duration: 0
entryType: "layout-shift"
hadRecentInput: false
lastInputTime: 0
name: ""
sources: (3) [LayoutShiftAttribution, LayoutShiftAttribution, LayoutShiftAttribution]
startTime: 11317.934999999125
value: 0.17508567530168798

Эта запись указывает на сдвиг макета, во время которого три DOM-элемента изменили своё положение. Оценка сдвига макета в этом отчёте составила 0.175.

Перечислим свойства экземпляра LayoutShift, которые наиболее важны для отладки сдвигов макета:

sources
Свойство sources перечисляет DOM-элементы, которые перемещались во время сдвига макета. Этот массив может содержать до пяти элементов. В случае, если смещение макета затронуло более пяти элементов, здесь будет сообщаться о пяти крупнейших (по оценке влияния на стабильность макета) источниках смещения макета. Эта информация передается с помощью интерфейса LayoutShiftAttribution (более подробно о нём будет ниже).
value
Свойство value содержит число — оценку (layout shift score) сдвига макета.
hadRecentInput
Свойство hadRecentInput указывает, произошел ли сдвиг макета в течение 500 миллисекунд после действий пользователя.
startTime
Свойство startTime указывает, когда произошел сдвиг макета. startTime указывается в миллисекундах и измеряется относительно времени, когда была инициирована загрузка страницы.
duration
Для свойства duration всегда будет установлено значение 0. Это свойство наследуется из интерфейса PerformanceEntry (интерфейс LayoutShift расширяет интерфейс PerformanceEntry). Однако концепция продолжительности не применяется к событиям сдвига макета, поэтому для неё установлено значение 0. Информацию об интерфейсе PerformanceEntry см. в спецификации.

Расширение Web Vitals может записывать информацию о сдвиге макета в консоль. Чтобы включить эту функцию, перейдите в «Options»> «Console Logging».

LayoutShiftAttribution

Интерфейс LayoutShiftAttribution описывает одиночный сдвиг одного DOM-элемента. Если во время сдвига макета смещаются несколько элементов, свойство sources будет возвращать несколько записей.

Например, приведенный ниже JSON соответствует сдвигу макета с одним источником: сдвиг вниз DOM-элемента <div id="banner"> с y: 76 на y: 246.

// ...
  "sources": [
    {
      "node": "div#banner",
      "previousRect": {
        "x": 311,
        "y": 76,
        "width": 4,
        "height": 18,
        "top": 76,
        "right": 315,
        "bottom": 94,
        "left": 311
      },
      "currentRect": {
        "x": 311,
        "y": 246,
        "width": 4,
        "height": 18,
        "top": 246,
        "right": 315,
        "bottom": 264,
        "left": 311
      }
    }
  ]

Свойство node идентифицирует сдвинутый HTML-элемент. При наведении указателя мыши на это свойство в DevTools, на странице подсвечивается соответствующий элемент.

Свойства previousRect и currentRect возвращают размер и положение узла

  • x и y возвращают координаты верхнего левого угла элемента.
  • Свойства width и height возвращают ширину и высоту элемента соответственно.
  • Свойства top, right, bottom и left возвращают значения координат x или y, соответствующие заданному краю элемента. Другими словами, значение top равно y; значение bottom равно y + height.

Если все свойства previousRect установлены в 0, это означает, что элемент переместился в поле зрения. Если все свойства currentRect установлены в 0, это означает, что элемент переместился из поля зрения.

Одна из наиболее важных вещей, которую следует понимать при интерпретации этих отчётов, заключается в том, что в качестве источников (sources) указываются элементы, которые перемещались во время сдвига макета. При этом они могут быть лишь косвенно связаны с основной причиной нестабильности макета. Вот несколько примеров.

Пример 1

В отчете об этом сдвиге макета будет возвращаться один источник: элемент B. Однако основной причиной этого сдвига макета было изменение размеров элемента A.

LayoutShifts при изменении размеров элемента
Пример 2

Сдвиг макета в этом примере будет возвращать два источника: элемент A и элемент B. Но основная причина этого смещения макета — изменение положения элемента A.

LayoutShifts при изменении положения элемента
Пример 3

Сдвиг макета в этом примере будет возвращать один источник: элемент B. Изменение положения элемента B здесь привело к смещению макета.

LayoutShifts при изменении положения элемента
Пример 4

Хотя элемент B меняет размер, в этом примере сдвига макета нет.

LayoutShifts отсутствует при изменении размеров элемента

Демонстрация влияния размеров и смещений DOM-элементов на сдвиг макета:

See this code Layout Shift Demo on x.xhtml.ru.

DevTools

Панель Performance

На панели «Experience» панели «Performance» в инструментах разработчика отображаются все сдвиги макета, которые происходят во время трассировки производительности, даже если они происходят в течение 500мс после взаимодействия с пользователем и, следовательно, не учитываются в CLS. При наведении указателя мыши на конкретный сдвиг макета на панели «Experience» выделяется затронутый DOM-элемент.

DevTools панель Performance

Чтобы просмотреть дополнительную информацию о сдвиге макета, надо кликнуть по блоку «Layout Shift», затем открыть панель «Summary». Изменения размеров элемента перечислены в формате [width, height], а изменения позиции элемента перечислены в формате [x, y]. Свойство «Had recent input» указывает, произошел ли сдвиг макета в течение 500мс после взаимодействия с пользователем.

DevTools Layout Shift Summary

Для получения информации о продолжительности сдвига макета надо открыть вкладку «Event Log». Продолжительность сдвига макета также можно приблизительно определить, посмотрев на панели «Experience» длину красного блока «Layout Shift».

DevTools Layout Shift Event Log

Продолжительность сдвига макета не влияет на его оценку. Для получения дополнительной информации об использовании панели «Performance» см. Справочник по анализу производительности.

Подсветка областей сдвига макета

Подсветка областей сдвига макета может быть полезным методом для быстрого и быстрого определения местоположения и времени сдвигов макета, происходящих на странице.

Чтобы включить области сдвига макета в DevTools, надо перейти в «Settings > More Tools > Rendering > Layout Shift Regions», затем обновить страницу, которая отлаживается. Области сдвига макета будут на короткое время выделены фиолетовым цветом.

Алгоритмы определения причин сдвигов макета

Независимо от того, когда и как происходит сдвиг макета, можно использовать следующие шаги, чтобы определить его причину. Эти шаги можно дополнить запуском Lighthouse — однако, он может определять только сдвиги макета, произошедшие во время начальной загрузки страницы. Кроме того, Lighthouse может предлагать только некоторые причины сдвигов макета — например, элементы изображения, у которых не указана ширина и высота.

Выявление причины сдвига макета

Сдвиг макета может быть вызван следующими событиями:

  • Изменения позиции DOM-элемента
  • Изменения размеров DOM-элемента
  • Добавление или удаление DOM-элемента
  • Анимации, влияющие на макет

Часто, DOM-элемент, непосредственно предшествующий сдвинутому элементу, будет тем, который, скорее всего, будет причиной сдвига макета. Таким образом, исследуя причину сдвига макета, следует учитывать:

  • Изменились ли положение и/или размеры предыдущего элемента?
  • Был ли перед смещенным элементом вставлен или удален DOM-элемент?
  • Было ли явно изменено положение смещенного элемента?

Если предыдущий элемент не вызвал сдвига макета, следует продолжить поиск, рассматривая другие предшествующие и соседние элементы.

Кроме того, направление и расстояние сдвига макета могут дать подсказки о первопричине. Например, большой сдвиг вниз часто указывает на вставку DOM-элемента, тогда как сдвиг на 1 или 2 пикселя часто указывает на применение конфликтующих стилей CSS или загрузку и применение веб-шрифта.

В этом примере замена шрифта привела к смещению элементов страницы вверх на пять пикселей.
В этом примере замена шрифта привела к смещению элементов страницы вверх на пять пикселей.

Вот некоторые из конкретных действий, которые наиболее часто вызывают события сдвига макета:

Изменения положения элемента (не из-за движения другого элемента)

Этот тип изменений часто является результатом:

  • Таблицы стилей загрузились слишком поздно или перезаписывают ранее объявленные стили.
  • Анимация и эффекты перехода.

Изменения размеров элемента

Этот тип изменений часто является результатом:

  • Таблицы стилей загрузились слишком поздно или перезаписывают ранее объявленные стили.
  • Изображения и фреймы указания размеров, которые загружаются после того, как их «слот» в макете был отрисован.
  • Текстовые блоки без фиксированных размеров, когда в них после визуализации текста изменяется шрифт.

Добавление или удаление DOM-элементов

Этот тип изменений часто является результатом:

  • Размещение рекламы и других сторонних вставок.
  • Вставка баннеров, предупреждений и модальных окон.
  • Бесконечная прокрутка и другие UX-шаблоны, которые загружают дополнительный контент перед существующим.

Анимации, влияющие на макет

Некоторые эффекты анимации могут переключать макет. Типичный пример этого — когда DOM-элементы «анимируются» методом изменения свойств, таких как top или left, вместо использования CSS-свойства transform.

Воспроизведение сдвигов макета

Сдвиги макета, которые нельзя воспроизвести, сложно исправить. Одна из самых простых, но наиболее эффективных вещей, которые можно сделать, чтобы лучше понять стабильность макета сайта — это потратить 5-10 минут на взаимодействие с ним, чтобы инициировать сдвиги макета. При этом должна быть открыта консоль и следует использовать Layout Instability API, чтобы получать сообщения о сдвигах макета.

Если сложно обнаружить сдвиги макета, можно попробовать это с другими устройствами и разными скоростями подключения. Например, использование более низкой скорости соединения может облегчить определение сдвигов макета. Можно использовать оператор debugger, чтобы упростить пошаговое изменение макета.

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (!entry.hadRecentInput) {
      cls += entry.value;
      debugger;
      console.log('Current CLS value:', cls, entry);
    }
  }
}).observe({type: 'layout-shift', buffered: true});