ResizeObserver vs Window Resize: когда использовать каждый из них
Вы создаёте компонент графика, который должен перерисовываться при уменьшении размера контейнера. Вы используете window.addEventListener('resize', ...), и это в основном работает — до тех пор, пока панель не изменит размер из-за сворачивания боковой панели, и ничего не срабатывает. Это основная проблема, которую рассматривает данная статья.
Ключевые выводы
- Событие
windowresize срабатывает только при изменении размера области просмотра браузера. Оно ничего не знает об отдельных DOM-элементах. ResizeObserverотслеживает конкретные элементы и срабатывает всякий раз, когда изменяются их размеры, независимо от причины.- Опрос размеров элементов после изменения области просмотра ненадёжен — он пропускает изменения от переключения CSS, динамического контента, загрузки шрифтов и многого другого.
- Для стилизации на основе контейнера без JavaScript предпочтительным инструментом являются CSS container queries.
Что на самом деле отслеживает каждый API
Событие window resize срабатывает при изменении размера области просмотра браузера. Это весь его охват. Оно ничего не знает об отдельных DOM-элементах.
ResizeObserver отслеживает конкретные элементы и срабатывает всякий раз, когда изменяются их размеры — независимо от того, что это вызвало. Изменение размера области просмотра, вставка DOM-элемента, переключение CSS-класса, перекомпоновка родительского flexbox — все это запускает наблюдатель, если размер элемента изменяется.
Это фундаментальное различие. Они отслеживают разные вещи и обычно выбираются для разных задач.
Когда использовать событие Window Resize
Событие window resize — правильный инструмент, когда ваша логика действительно зависит от размеров области просмотра, а не от размера какого-либо конкретного элемента:
- Переключение макета мобильной навигации на контрольной точке области просмотра
- Изменение размера полностраничного canvas или WebGL-контекста до
window.innerWidth/window.innerHeight - Настройка глобального состояния макета, которое зависит от доступного пространства экрана
window.addEventListener('resize', () => {
canvas.width = window.innerWidth
canvas.height = window.innerHeight
render()
})
Для чисто визуальных, управляемых CSS реакций на размер области просмотра предпочтительнее использовать медиа-запросы вместо JavaScript.
Когда использовать ResizeObserver
Сценарии использования ResizeObserver сосредоточены на поведении на уровне компонента, которое должно реагировать на фактический отрендеренный размер элемента:
- График, который пересчитывает шкалы осей при изменении ширины контейнера
- Текстовый редактор, который настраивает макет панели инструментов в зависимости от доступного пространства
- Изменяемая панель или виджет с разделёнными областями
- Любой встроенный виджет, который не управляет своим собственным контейнером
const ro = new ResizeObserver((entries) => {
for (const entry of entries) {
const { inlineSize, blockSize } = entry.contentBoxSize[0]
chart.resize(inlineSize, blockSize)
}
})
ro.observe(document.querySelector('.chart-container'))
Чтение современной формы API
Текущий API ResizeObserver предоставляет массивы contentBoxSize и borderBoxSize для каждой записи — каждый содержит inlineSize (ширина в горизонтальных режимах письма) и blockSize (высота). Более старое свойство contentRect всё ещё работает, но существует в первую очередь для обратной совместимости. Предпочтительнее использовать contentBoxSize[0] в новом коде.
Discover how at OpenReplay.com.
Почему бы просто не опрашивать после Window Resize?
Распространённый паттерн — прослушивание события window resize с последующим вызовом getBoundingClientRect() для элементов — ненадёжен. Он пропускает изменения размера, которые не имеют ничего общего с областью просмотра: загрузку динамического контента, переключённые CSS-классы, внедрённые дочерние элементы, загрузку шрифтов. ResizeObserver отлавливает все эти случаи. Опрос после изменения размера области просмотра — нет.
Производительность и циклы обратной связи
Колбэки ResizeObserver выполняются после компоновки и перед отрисовкой, что означает, что чтения DOM внутри колбэка отражают текущую компоновку без принудительного reflow. Это структурно безопаснее, чем чтение свойств компоновки внутри обработчика события resize.
Одна особенность: если вы изменяете размер наблюдаемого элемента внутри его собственного колбэка, вы запускаете ещё одно наблюдение. ResizeObserver обрабатывает это, обрабатывая наблюдения на прогрессивно более глубоких уровнях DOM в пределах одного кадра и в конечном итоге сообщая об ошибке, если цикл не разрешается — предотвращая бесконечные циклы. Вы всё равно должны быть осознанными в отношении того, что вы пишете внутри колбэка.
Преимущество производительности ResizeObserver становится значимым, когда ваш колбэк затрагивает компоновку или когда изменения размера элемента происходят независимо от изменения размера области просмотра.
Альтернатива CSS Container Queries
Если ваша цель — стилизация элемента на основе размера его контейнера, CSS container queries — правильный инструмент, JavaScript не требуется. Резервируйте ResizeObserver для случаев, когда логика времени выполнения, а не стилизация, должна реагировать на изменения размера.
| Сценарий | Лучший инструмент |
|---|---|
| Изменения макета на уровне области просмотра | Событие window resize |
| Размеры элемента управляют JS-логикой | ResizeObserver |
| Только стилизация на основе контейнера | CSS container queries |
Заключение
Используйте событие window resize, когда вас интересует область просмотра. Используйте ResizeObserver, когда вас интересует элемент. Используйте CSS container queries, когда вам нужно только изменить стили. Эти три инструмента покрывают всё пространство адаптивного поведения — выбирайте тот, который соответствует тому, что вы на самом деле измеряете.
Часто задаваемые вопросы
ResizeObserver срабатывает при изменении размеров элемента, поэтому сворачивание элемента до нулевой ширины или высоты запустит его. Однако он не срабатывает при изменениях видимости или прозрачности, которые оставляют размеры нетронутыми. Для истинного обнаружения видимости используйте вместо этого IntersectionObserver.
Наблюдатель автоматически прекращает отслеживание элемента после того, как он будет собран сборщиком мусора. Однако явный вызов unobserve или disconnect является хорошей практикой, особенно в одностраничных приложениях или компонентах фреймворка с жизненными циклами монтирования и размонтирования, чтобы избежать устаревших ссылок и неожиданных колбэков.
Да. ResizeObserver имеет широкую поддержку в Chrome, Firefox, Safari и Edge. Если вам нужно поддерживать старые браузеры, доступен полифил, хотя он внутренне полагается на опрос и mutation observers, поэтому он не будет соответствовать нативной производительности.
Обычно нет для лёгких обновлений UI. ResizeObserver уже эффективно группирует уведомления в рамках конвейера рендеринга. Однако, если ваш колбэк запускает дорогостоящую последующую работу, такую как сетевые запросы или тяжёлые вычисления, применяйте debounce к этой конкретной работе, а не ко всему колбэку.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.