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