ResizeObserver
сообщает об изменении размера элемента.
До ResizeObserver
вам приходилось прикреплять слушателя к событию resize
документа, чтобы получать уведомления о любых изменениях размеров области просмотра. В обработчике событий вам затем приходилось выяснять, какие элементы были затронуты этим изменением, и вызывать определенную процедуру для соответствующей реакции. Если вам требовались новые размеры элемента после изменения размера, вам приходилось вызывать getBoundingClientRect()
или getComputedStyle()
, что могло привести к перегрузке макета, если вы не позаботились о пакетировании всех своих чтений и всех своих записей.
Это даже не покрывало случаи, когда элементы изменяют свой размер без изменения размера главного окна. Например, добавление новых дочерних элементов, установка стиля display
элемента на none
или аналогичные действия могут изменить размер элемента, его родственных элементов или его предков.
Вот почему ResizeObserver
— полезный примитив. Он реагирует на изменения размера любого из наблюдаемых элементов, независимо от того, что вызвало изменение. Он также предоставляет доступ к новому размеру наблюдаемых элементов.
API
Все API с суффиксом Observer
, упомянутые выше, имеют простую конструкцию API. ResizeObserver
не является исключением. Вы создаете объект ResizeObserver
и передаете обратный вызов конструктору. Обратный вызов передается массиву объектов ResizeObserverEntry
— по одной записи на наблюдаемый элемент — который содержит новые измерения для элемента.
var ro = new ResizeObserver(entries => {
for (let entry of entries) {
const cr = entry.contentRect;
console.log('Element:', entry.target);
console.log(`Element size: ${cr.width}px x ${cr.height}px`);
console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
}
});
// Observe one or multiple elements
ro.observe(someElement);
Некоторые подробности
О чем сообщается?
Обычно ResizeObserverEntry
сообщает о поле содержимого элемента через свойство contentRect
, которое возвращает объект DOMRectReadOnly
. Поле содержимого — это поле, в которое может быть помещено содержимое. Это поле границы за вычетом отступа.

Важно отметить, что хотя ResizeObserver
сообщает и размеры contentRect
, и отступы, он отслеживает только contentRect
. Не путайте contentRect
с ограничивающим прямоугольником элемента. Ограничивающий прямоугольник, как сообщает getBoundingClientRect()
, — это прямоугольник, содержащий весь элемент и его потомков. SVG являются исключением из правила, где ResizeObserver
будет сообщать размеры ограничивающего прямоугольника.
Начиная с Chrome 84, ResizeObserverEntry
имеет три новых свойства для предоставления более подробной информации. Каждое из этих свойств возвращает объект ResizeObserverSize
, содержащий свойство blockSize
и свойство inlineSize
. Эта информация относится к наблюдаемому элементу на момент вызова обратного вызова.
-
borderBoxSize
-
contentBoxSize
-
devicePixelContentBoxSize
Все эти элементы возвращают массивы только для чтения, поскольку в будущем есть надежда, что они смогут поддерживать элементы, имеющие несколько фрагментов, которые встречаются в сценариях с несколькими столбцами. На данный момент эти массивы будут содержать только один элемент.
Поддержка этих свойств на платформе ограничена, но Firefox уже поддерживает первые два.
Когда об этом сообщат?
Спецификация предписывает, чтобы ResizeObserver
обрабатывал все события изменения размера до отрисовки и после макета. Это делает обратный вызов ResizeObserver
идеальным местом для внесения изменений в макет вашей страницы. Поскольку обработка ResizeObserver
происходит между макетом и отрисовкой, это сделает недействительным только макет, но не отрисовку.
Попался
Вы можете спросить себя: что произойдет, если я изменю размер наблюдаемого элемента внутри обратного вызова в ResizeObserver
? Ответ таков: вы немедленно вызовете другой обратный вызов. К счастью, ResizeObserver
имеет механизм, позволяющий избежать бесконечных циклов обратного вызова и циклических зависимостей. Изменения будут обработаны в том же кадре, только если элемент с измененным размером находится глубже в дереве DOM, чем самый мелкий элемент, обработанный в предыдущем обратном вызове. В противном случае они будут отложены до следующего кадра.
Приложение
Одна из вещей, которую ResizeObserver
позволяет вам делать, — это реализовывать медиа-запросы для каждого элемента. Наблюдая за элементами, вы можете императивно определять контрольные точки дизайна и изменять стили элемента. В следующем примере второй блок будет менять радиус своей границы в соответствии со своей шириной.
const ro = new ResizeObserver(entries => {
for (let entry of entries) {
entry.target.style.borderRadius =
Math.max(0, 250 - entry.contentRect.width) + 'px';
}
});
// Only observe the second box
ro.observe(document.querySelector('.box:nth-child(2)'));
Другой интересный пример — окно чата. Проблема, которая возникает в типичном макете разговора сверху вниз, — это позиционирование прокрутки. Чтобы не сбивать пользователя с толку, полезно, если окно прилипает к нижней части разговора, где появляются самые новые сообщения. Кроме того, любое изменение макета (например, переход телефона из альбомной ориентации в портретную или наоборот) должно приводить к тому же результату.
ResizeObserver
позволяет вам написать один фрагмент кода, который обрабатывает оба сценария. Изменение размера окна — это событие, которое ResizeObserver
может перехватить по определению, но вызов appendChild()
также изменяет размер этого элемента (если не установлено overflow: hidden
), поскольку ему нужно освободить место для новых элементов. Учитывая это, для достижения желаемого эффекта требуется совсем немного строк:
const ro = new ResizeObserver(entries => {
document.scrollingElement.scrollTop =
document.scrollingElement.scrollHeight;
});
// Observe the scrollingElement for when the window gets resized
ro.observe(document.scrollingElement);
// Observe the timeline to process new messages
ro.observe(timeline);
Довольно мило, да?
Отсюда я мог бы добавить больше кода для обработки случая, когда пользователь прокрутил страницу вручную и хочет, чтобы прокрутка закрепилась на этом сообщении при поступлении нового сообщения.
Другой вариант использования — для любого типа пользовательского элемента, который делает свою собственную компоновку. До ResizeObserver
не было надежного способа получать уведомления об изменении его размеров, чтобы его дочерние элементы можно было снова выложить.
Влияние на взаимодействие со следующей краской (INP)
Interaction to Next Paint (INP) — это метрика, которая измеряет общую отзывчивость страницы на взаимодействие с пользователем. Если INP страницы находится в «хорошем» пороге — то есть 200 миллисекунд или меньше — можно сказать, что страница надежно реагирует на взаимодействие с ней пользователя.
Хотя количество времени, необходимое для выполнения обратных вызовов событий в ответ на взаимодействие с пользователем, может вносить значительный вклад в общую задержку взаимодействия, это не единственный аспект INP, который следует учитывать. INP также учитывает количество времени, необходимое для следующей отрисовки взаимодействия. Это количество времени, необходимое для завершения работы по рендерингу, необходимой для обновления пользовательского интерфейса в ответ на взаимодействие.
Что касается ResizeObserver
, это важно, поскольку обратный вызов, который запускает экземпляр ResizerObserver
, происходит непосредственно перед работой по рендерингу. Это сделано намеренно, поскольку работа, которая происходит в обратном вызове, должна быть принята во внимание, поскольку результат этой работы, скорее всего, потребует изменения пользовательского интерфейса.
Постарайтесь сделать как можно меньше работы по рендерингу, необходимой для обратного вызова ResizeObserver
, поскольку чрезмерная работа по рендерингу может привести к ситуациям, когда браузер задерживается в выполнении важной работы. Например, если какое-либо взаимодействие имеет обратный вызов, который вызывает обратный вызов ResizeObserver
, убедитесь, что вы делаете следующее, чтобы обеспечить максимально плавный опыт:
- Убедитесь, что ваши селекторы CSS максимально просты, чтобы избежать чрезмерной работы по пересчету стилей . Пересчет стилей происходит непосредственно перед макетом, а сложные селекторы CSS могут задерживать операции макета.
- Избегайте выполнения любых действий в обратном вызове
ResizeObserver
, которые могут вызвать принудительную перекомпоновку . - Время, необходимое для обновления макета страницы, обычно увеличивается с количеством элементов DOM на странице. Хотя это верно независимо от того, используют ли страницы
ResizeObserver
, работа, выполняемая в обратном вызовеResizeObserver
, может стать значительной по мере увеличения структурной сложности страницы.
Заключение
ResizeObserver
доступен во всех основных браузерах и обеспечивает эффективный способ мониторинга изменений размеров элементов на уровне элементов. Просто будьте осторожны, чтобы не задерживать рендеринг слишком сильно с этим мощным API.