ResizeObserver сообщает вам об изменении размера элемента.
До появления ResizeObserver для получения уведомлений об изменении размеров области просмотра необходимо было прикрепить обработчик события resize документа. В обработчике события нужно было определить, какие элементы были затронуты этим изменением, и вызвать соответствующую процедуру для реагирования. Если после изменения размера требовались новые размеры элемента, приходилось вызывать getBoundingClientRect() или getComputedStyle() , что могло привести к сбоям компоновки, если не обеспечить пакетную обработку всех операций чтения и записи .
Это даже не охватывало случаи, когда элементы изменяют свой размер без изменения размера основного окна. Например, добавление новых дочерних элементов, установка стиля display элемента в none или подобные действия могут изменить размер элемента, его соседей или его предков.
Именно поэтому ResizeObserver — полезный примитив. Он реагирует на изменения размера любого из наблюдаемых элементов, независимо от причины изменения. Он также предоставляет доступ к новому размеру наблюдаемых элементов.
API
Все API с суффиксом Observer , упомянутые выше, имеют простую архитектуру. 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)
Показатель «Взаимодействие до следующей отрисовки» (INP) измеряет общую скорость отклика страницы на действия пользователя. Если значение INP страницы находится в «хорошем» диапазоне — то есть 200 миллисекунд или меньше — можно сказать, что страница надежно реагирует на действия пользователя.
Хотя время, необходимое для выполнения обратных вызовов событий в ответ на взаимодействие с пользователем, может существенно влиять на общую задержку взаимодействия, это не единственный аспект INP, который следует учитывать. INP также учитывает время, необходимое для следующей отрисовки взаимодействия. Это время, необходимое для завершения работы по рендерингу, необходимой для обновления пользовательского интерфейса в ответ на взаимодействие.
В контексте ResizeObserver это важно, поскольку функция обратного вызова, выполняемая экземпляром ResizerObserver , запускается непосредственно перед началом рендеринга. Это сделано намеренно, так как необходимо учитывать работу, выполняемую в этой функции обратного вызова, поскольку результат этой работы, скорее всего, потребует изменения пользовательского интерфейса.
Старайтесь выполнять в коллбэке ResizeObserver как можно меньше работы по отрисовке, поскольку избыточная работа может привести к задержкам в выполнении важных задач браузером. Например, если какое-либо взаимодействие содержит коллбэк, который вызывает коллбэк ResizeObserver , убедитесь, что вы выполняете следующие действия для обеспечения максимально плавной работы:
- Чтобы избежать излишнего пересчета стилей , убедитесь, что ваши CSS-селекторы максимально просты. Пересчет стилей происходит непосредственно перед компоновкой, а сложные CSS-селекторы могут задерживать операции компоновки.
- Избегайте выполнения в функции обратного вызова
ResizeObserverлюбых действий, которые могут вызвать принудительную перерисовку страницы . - Время, необходимое для обновления макета страницы, обычно увеличивается с количеством элементов DOM на странице. Хотя это верно независимо от того, использует ли страница
ResizeObserverили нет, объем работы, выполняемой в коллбэкеResizeObserver, может значительно возрасти по мере увеличения структурной сложности страницы.
Заключение
ResizeObserver доступен во всех основных браузерах и предоставляет эффективный способ отслеживания изменения размера элементов на уровне отдельных элементов. Однако следует проявлять осторожность, чтобы не задерживать рендеринг слишком сильно при использовании этого мощного API.