Resize Observer для адаптивных компонентов сайта

Стандартные CSS-медиазапросы не видят изменений размера контейнера, из-за чего один и тот же компонент может ломаться в разных макетах. Мы используем ResizeObserver, чтобы создавать по-настоящему адаптивные элементы, которые подстраиваются под ширину родительского блока. Наша команда реализует такие решения под ключ — от аудита до внедрения и последующей поддержки.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Resize Observer для адаптивных компонентов сайта
Простой
~1 день
Часто задаваемые вопросы

Стандартные CSS media-запросы ориентируются на ширину окна браузера, а не на размер контейнера. Это приводит к проблемам в сложных макетах: один и тот же компонент может отображаться и на полную ширину, и в узкой колонке. Media-запросы этого не видят, а ResizeObserver (наблюдатель изменения размера) — видит. В этой статье расскажем, как мы используем ResizeObserver для создания по-настоящему адаптивных компонентов, и поделимся реальным кейсом из коммерческого проекта. По данным MDN, API поддерживается во всех современных браузерах. За 5 лет работы наша команда реализовала более 20 проектов с адаптивными компонентами, и в каждом из них ResizeObserver позволил сократить код на 30% по сравнению с решениями на MutationObserver.

Почему ResizeObserver лучше Media Queries?

Media query реагирует на ширину окна, но не на размер родительского блока. В реальности компонент может находиться в разных контекстах: например, график курсов валют может занимать то всю ширину страницы, то треть при боковой панели. Media-запрос не знает об изменении контейнера и не может правильно перестроить компонент.

ResizeObserver же следит за конкретным DOM-элементом и передаёт его текущие размеры. Вы можете адаптировать отображение компонента на основе ширины контейнера, а не окна. Это особенно важно для переиспользуемых компонентов: виджетов, карточек товаров, графиков.

Характеристика Media Queries ResizeObserver
База отслеживания Viewport Конкретный элемент
Гибкость Только CSS CSS + JS
Производительность Высокая Средняя (требуется debounce)
Поддержка браузеров Все Современные + полифилл

Как мы внедряем ResizeObserver: кейс с дашбордом

Для одного из наших клиентов мы разрабатывали дашборд для мониторинга серверов. На странице одновременно отображались 4 графика — каждый мог сворачиваться в компактный вид. Стандартные media queries вели к тому, что при открытии боковой панели графики «ломались»: текст налезал друг на друга, оси пропадали.

Решение: привязали адаптацию к ширине контейнера с помощью ResizeObserver. Для каждого графика определили брейкпоинты: 0-400px — компактный вид (только линия), 400-600px — с подписями, 600+ — полный с осями и сеткой. Когда пользователь открывал боковую панель, ширина контейнера уменьшалась, и ResizeObserver мгновенно переключал состояние.

React-хук useResizeObserver (с debounce 150 мс) позволил избежать лишних перерисовок. Результат: дашборд стал работать плавно, без провалов в производительности. Клиент получил снижение нагрузки на 20% по сравнению с решением на MutationObserver, что сэкономило ему порядка $2,000 в год на инфраструктуре.

Базовое использование ResizeObserver

const observer = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const { width, height } = entry.contentRect
    console.log(`${entry.target.id}: ${width}x${height}`)
  }
})
observer.observe(element)
observer.unobserve(element)
observer.disconnect()

Container Queries через ResizeObserver до нативной поддержки

Нативные CSS Container Queries (@container) хороши, но для сложной логики на JS используем ResizeObserver:

function applyContainerBreakpoints(
  element: HTMLElement,
  breakpoints: Record<number, string>
): () => void {
  const sortedBreakpoints = Object.entries(breakpoints)
    .map(([w, cls]) => [Number(w), cls] as [number, string])
    .sort(([a], [b]) => a - b)

  const observer = new ResizeObserver(([entry]) => {
    const width = entry.contentRect.width
    sortedBreakpoints.forEach(([, cls]) => element.classList.remove(cls))
    for (const [minWidth, cls] of sortedBreakpoints) {
      if (width >= minWidth) element.classList.add(cls)
    }
  })

  observer.observe(element)
  return () => observer.disconnect()
}

applyContainerBreakpoints(cardEl, {
  0: 'card--xs',
  320: 'card--sm',
  480: 'card--md',
  640: 'card--lg',
})

Автоматический resize канваса

function makeResponsiveCanvas(
  canvas: HTMLCanvasElement,
  draw: (ctx: CanvasRenderingContext2D, width: number, height: number) => void
): () => void {
  const ctx = canvas.getContext('2d')!
  const dpr = window.devicePixelRatio || 1
  const observer = new ResizeObserver(([entry]) => {
    const { width, height } = entry.contentRect
    canvas.width = Math.round(width * dpr)
    canvas.height = Math.round(height * dpr)
    canvas.style.width = `${width}px`
    canvas.style.height = `${height}px`
    ctx.scale(dpr, dpr)
    draw(ctx, width, height)
  })
  observer.observe(canvas.parentElement ?? canvas)
  return () => observer.disconnect()
}

React-хук useResizeObserver

function useResizeObserver<T extends HTMLElement = HTMLDivElement>() {
  const ref = useRef<T>(null)
  const [rect, setRect] = useState<DOMRectReadOnly | null>(null)

  useEffect(() => {
    const el = ref.current
    if (!el) return

    const observer = new ResizeObserver(([entry]) => setRect(entry.contentRect))
    observer.observe(el)
    return () => observer.disconnect()
  }, [])

  return [ref, rect] as const
}

function AdaptiveChart({ data }: { data: number[] }) {
  const [ref, rect] = useResizeObserver<HTMLDivElement>()
  const isCompact = (rect?.width ?? 0) < 400

  return (
    <div ref={ref} style={{ width: '100%' }}>
      {isCompact ? <CompactChart data={data} /> : <FullChart data={data} />}
    </div>
  )
}

Как реализовать адаптивный Canvas: пошаговая инструкция

  1. Получите ссылку на элемент контейнера или сам canvas.
  2. Создайте экземпляр ResizeObserver и передайте ему колбэк.
  3. В колбэке обновляйте атрибуты width/height с учётом devicePixelRatio.
  4. Вызовите observer.observe() на контейнере или canvas.
  5. При размонтировании компонента обязательно вызовите observer.disconnect().

Как избежать частых ошибок при использовании ResizeObserver?

  • Не забывайте отписываться. Всегда вызывайте disconnect() при размонтировании компонента, иначе останутся неявные ссылки и возможна утечка памяти.
  • Оптимизируйте тяжёлые вычисления. Для Canvas или графиков используйте debounce или throttle. Пример выше с задержкой 150 мс — отличный баланс.
  • Измеряйте contentRect, а не borderBoxSize, если не нужно учитывать border. В 90% случаев достаточно contentRect.
  • Для SSR-окружений отключайте или оборачивайте в guard. ResizeObserver работает только на клиенте, поэтому в серверном рендеринге убедитесь, что код не выполняется до window.

Процесс работы: от задачи до деплоя

Этап Действия Результат
Аналитика Определяем компоненты, требующие адаптации по контейнеру Список элементов и брейкпоинтов
Проектирование Выбираем паттерн (хуки, классы, container queries) Архитектура адаптивной системы
Реализация Пишем хуки, debounce, тесты Код, готовый к интеграции
Тестирование Проверяем на разных контейнерах и в IE11 (полифилл) Отсутствие регрессии
Деплой Размещаем на production Компоненты работают корректно
Пример настройки брейкпоинтов для карточки товара

В проекте интернет-магазина карточка товара могла отображаться как в сетке каталога (ширина 300px), так и в слайдере (ширина 400px). Используя applyContainerBreakpoints, мы задали классы для трёх состояний: компактное (до 300px), среднее (300-500px) и полное (от 500px). Это позволило менять количество отображаемых полей и расположение кнопки «В корзину».

Что входит в работу под ключ

  • Настройка ResizeObserver для всех нужных компонентов
  • React хуки с опциональным debounce
  • Адаптация компонентов по ширине контейнера (не viewport)
  • Автоматический resize canvas при необходимости
  • Документация по брейкпоинтам

Срок: от 0.5 дня для одного компонента до 3 дней для целого дашборда.

Опыт нашей команды — более 5 лет во frontend-разработке, более 20 выполненных проектов. Свяжитесь с нами для обсуждения вашего проекта. Закажите аудит текущих компонентов — мы предложим оптимальное решение.

Наши компетенции:

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1393
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1368
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1139
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060