Руководство по использованию ResizeObserver в JavaScript

В этой статье изучим API Resize Observer, с помощью которого мы можем следить за изменениями размеров элементов. По сути он представляет собой своего рода аналог события resize
для элементов.
Что такое Resize Observer?
Resize Observer — это новый web API, который очень похож на другие интерфейсы наблюдателей (Intersection Observer и Mutation Observer). С помощью него мы можем следить за изменениями размеров конкретных элементов.
При этом необходимо отметить, что Resize Observer не выполняет слежение за элементами во время их CSS трансформаций, а также не работает с незаменяемыми инлайновыми элементами. Под незаменяемыми инлайновыми элементами понимаются такие, которые отображаются так как есть. То есть вместо которых не появляется какой-то внешнее заменяющее их содержимое (например, изображение, аудио, видео и так далее).
Кроме этих особенностей, Resize Observer имеет ещё другие. Он информирует нас, когда:
- наблюдаемый элемент вставляется или удаляется из DOM;
- свойство
display
наблюдаемого элемента принимает значениеnone
.
В настоящее время Resize Observer – это единственный способ, с помощью которого мы можем реагировать на изменение размера того или иного элемента. По сути – это своего рода аналог события resize
объекта window
, но только для элементов.
Пример использования события resize
:
window.onresize = () => {
// ...
}
// или с помощью addEventListener
window.addEventListener('resize', () => {
// ...
});
При этом событие resize
будет происходить при каждом изменении размеров области просмотра, а не только когда фактически изменяется размер интересующего нас элемента.
В результате, использование resize
может привести к большим проблемам с производительностью, чем применение Resize Observer. Так как количество возникающий событий будет больше и для их обработки потребуется намного больше времени и ресурсов.
На текущий момент, Resize Observer имеет поддержку во всех основных современных браузерах:
- Chrome >= 64;
- Edge >= 79;
- Firefox >= 69;
- Safari >= 13.1.
Посмотреть это более детально можно на ресурсе «Can I use...».

Как использовать Resize Observer?
Использование Resize Observer осуществляется путем создания нового экзмепляра класса ResizeObserver
и использовании в качестве аргумента функции обратного вызова callback
:
const resizeObserver = new ResizeObserver(callback);
В качестве callback
обычно используют анонимную стрелочную функцию:
const resizeObserver = new ResizeObserver((entries, observer) => {
console.log(entries);
console.log(observer);
});

Эта функция будет вызываться всякий раз, когда будет происходить изменение размеров у наблюдаемых элементов.
Она имеет 2 параметра:
entries
– содержит массив объектовResizeObserverEntry
, который мы можем использовать для получения новых размеров элемента.observer
– хранит ссылку на самResizeObserver
, с помощью которой мы можем вызовать методы этого объекта внутри колбэк функции.
Параметр entries
, как мы уже отметили выше, содержит массив объектов. Чтобы их перебрать мы можем, например, использовать метод forEach
:
const resizeObserver = new ResizeObserver((entries, observer) => {
entries.forEach((entry) => {
console.log(entry);
});
});

Объект ResizeObserverEntry
имеет следующие свойства:
borderBoxSize
– общие размеры элемента (включаютpadding
иborder
);contentBoxSize
– размеры контентной области;contentRect
– возвращает объектDOMRectReadOnly
, содержащий информацию о положении и размерах элемента;devicePixelContentBoxSize
– возвращает размеры элемента в физических пикселях;target
– содержит наблюдаемый DOM-элемент, размеры которого изменились.
Свойства borderBoxSize
, contentBoxSize
и devicePixelContentBoxSize
возвращают объект ResizeObserverSize
. Он включает в себя два свойства: inlineSize
и blockSize
. Они будут обозначать ширину и высоту элемента, при условии, что мы работаем в горизонтальном режиме письма.
Последнее, что нужно сделать – это добавить элементы (Element
или SVGElement
) за изменениями размеров которых resizeObserver
должен наблюдать. Осуществляется это с помощью метода observe
. Например, добавим 2 элемента:
resizeObserver.observe(document.querySelector('.box-1'));
resizeObserver.observe(document.querySelector('.box-2'));
В результате код, необходимый для наблюдения за изменениями размеров двух элементов, будет следующим:
const resizeObserver = new ResizeObserver((entries, observer) => {
entries.forEach((entry) => {
const width = entry.borderBoxSize[0].blockSize.toFixed(2);
const height = entry.borderBoxSize[0].inlineSize.toFixed(2);
entry.target.textContent = `${width} x ${height}`;
});
});
resizeObserver.observe(document.querySelector('.box-1'));
resizeObserver.observe(document.querySelector('.box-2'));

Данная задача до появления ResizeObserver
, то есть с помощью события resize
решалась бы следующим образом:
const elBoxes = document.querySelectorAll('.box');
const resizeBoxes = () => {
elBoxes.forEach((elBox) => {
const clientRect = elBox.getBoundingClientRect();
const width = clientRect.width.toFixed(2);
const height = clientRect.height.toFixed(2);
elBox.textContent = `${width} x ${height}`;
});
}
window.addEventListener('resize', resizeBoxes);
resizeBoxes();
Методы unobserve() и disconnect()
Кроме observe()
, с помощью которого мы можем включить слежение за элементом, имеются ещё методы unobserve()
и disconnect()
.
Метод unobserve()
позволяет остановить слежение за указанным элементом. Для этого элемент за которым нужно прекратить наблюдение необходимо передать в качестве аргумента:
// el – элемент, за которым нужно прекратить наблюдение
resizeObserver.unobserve(el);
Метод disconnect()
предназначен, когда нужно остановить наблюдение за всеми элементами:
resizeObserver.disconnect();
Примеры использования Resize Observer
1. Изменение размера шрифта в зависимости от ширины элемента:
// DOM-элемент, содержащий контент
const elContent = document.querySelector('.content');
// чекбокс, определяющий нужно ли сделать размер шрифта адаптивным
const checkbox = document.querySelector('#fz-adaptive');
//создаем экземпляр ResizeObserver
const resizeObserver = new ResizeObserver((entries, observer) => {
entries.forEach((entry) => {
// вычисленный размер шрифта, который получаем путём деления ширины элемента на 800
const calcFontSize = entry.contentBoxSize[0].inlineSize / 800;
// в качестве размера шрифта устанавливаем большое из двух значений: 1 и calcFontSize
elContent.style.fontSize = `${Math.max(1, calcFontSize )}rem`;
});
});
// устанавливаем наблюдение за элементом elContent
resizeObserver.observe(elContent);
// при клике на чекбокс
checkbox.addEventListener('change', (e) => {
if (checkbox.checked) {
// устанавливаем наблюдение за элементом elContent
resizeObserver.observe(elContent);
} else {
// удаляем наблюдение за элементом elContent
resizeObserver.unobserve(elContent);
// устанавливаем в качестве размера шрифта значение 1rem
elContent.style.fontSize = '1rem';
}
});

2. Создание графики на <canvas>
, которая перерисовывается при изменении размера холста:
const elCanvas = document.querySelector('canvas');
const context = elCanvas.getContext('2d');
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
const width = entry.borderBoxSize[0].inlineSize;
const height = entry.borderBoxSize[0].blockSize;
[elCanvas.width, elCanvas.height] = [width, height];
context.clearRect(0, 0, width, height);
const cx = width / 2;
const cy = height / 2;
const k = Math.min(width, height) / 150;
context.lineWidth = 5 * k;
context.fillStyle = '#f0cf28';
context.beginPath();
context.arc(cx, cy, 50 * k, 0, Math.PI * 2, true);
context.fill();
context.beginPath();
context.arc(cx, cy, 25 * k, 0 + Math.PI / 8, Math.PI - Math.PI / 8, false);
context.stroke();
context.fillStyle = '#000';
context.beginPath();
context.arc(cx - 20 * k, cy - 10 * k, 5 * k, 0, Math.PI * 2, true); // Left eye
context.fill();
context.arc(cx + 20 * k, cy - 10 * k, 5 * k, 0, Math.PI * 2, true); // Right eye
context.fill();
});
});
resizeObserver.observe(document.documentElement);

3. Слайдер, в котором величина смещения слайдов будет перерасчитываться при изменении его размеров:
const elSlider = document.querySelector('.slider');
const elSliderItems = document.querySelector('.slider-items');
const calcWidthItem = (widthItem) => {
const width = elSlider.getBoundingClientRect().width;
const count = Math.floor(width / widthItem);
const gaps = width - widthItem * count;
return widthItem + gaps / (count - 1);
}
const transform = () => {
const index = elSlider.dataset.index ?? 0;
const value = -elSlider.dataset.width * elSlider.dataset.index;
elSliderItems.style.transform = `translateX(${value}px)`;
}
document.addEventListener('click', (e) => {
if (e.target && !e.target.closest('.slider-btn')) {
return;
}
const btnSlider = e.target.closest('.slider-btn');
elSlider.dataset.index = btnSlider.dataset.index;
transform();
});
const resizeObserver = new ResizeObserver((entries) => {
entries.forEach((entry) => {
elSlider.dataset.width = calcWidthItem(entry.borderBoxSize[0].inlineSize);
transform();
});
});
resizeObserver.observe(document.querySelector('.slider-item'));

Комментарии: 0