ResizeObserver: изменение размера

ResizeObserver — браузерный API для асинхронного наблюдения за изменениями размеров конкретного элемента, более точный и производительный чем слушатель события resize на window.

Зачем нужно

Событие window.resize срабатывает только при изменении размера окна, но элемент может менять размер из-за CSS-изменений, динамического контента, flex/grid-пересчёта — независимо от окна. ResizeObserver наблюдает за конкретным элементом и вызывает callback асинхронно (после layout, перед paint), не блокируя рендеринг.

Где используется

  • Адаптивные canvas и SVG (пересчёт при изменении контейнера)
  • Виртуальные списки (recalc при изменении высоты элементов)
  • Компоненты-диаграммы и графики (Chart.js, D3)
  • Responsive component design (не window, а конкретный блок)
  • Infinite scroll с динамической высотой элементов

Основной контент

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

const observer = new ResizeObserver((entries) => {
  entries.forEach(entry => {
    const { width, height } = entry.contentRect;
    console.log(`Новый размер: ${width}px x ${height}px`);

    // Альтернатива через borderBoxSize (с паддингами)
    if (entry.borderBoxSize) {
      const [box] = entry.borderBoxSize;
      console.log(`Border box: ${box.inlineSize}px x ${box.blockSize}px`);
    }
  });
});

const element = document.querySelector('.resizable-container');
observer.observe(element);

// Остановить наблюдение
observer.unobserve(element);
observer.disconnect(); // остановить всё

Адаптивный canvas

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');

const resizeObserver = new ResizeObserver(entries => {
  for (const entry of entries) {
    const { width, height } = entry.contentRect;

    // Обновляем размер canvas с учётом devicePixelRatio
    const dpr = window.devicePixelRatio || 1;
    canvas.width = width * dpr;
    canvas.height = height * dpr;
    ctx.scale(dpr, dpr);

    // Перерисовываем содержимое
    redraw(ctx, width, height);
  }
});

resizeObserver.observe(canvas.parentElement);

function redraw(ctx, width, height) {
  ctx.clearRect(0, 0, width, height);
  ctx.fillStyle = '#4a90e2';
  ctx.fillRect(10, 10, width - 20, height - 20);
}

Responsive компонент

class ResponsiveChart {
  constructor(container) {
    this.container = container;
    this.observer = new ResizeObserver(entries => {
      const entry = entries[0];
      const { width } = entry.contentRect;
      this.updateLayout(width);
    });
    this.observer.observe(container);
  }

  updateLayout(width) {
    if (width < 400) {
      this.container.classList.add('compact');
    } else {
      this.container.classList.remove('compact');
    }
    this.render(width);
  }

  render(width) {
    // перерисовать с новой шириной
  }

  destroy {
    this.observer.disconnect();
  }
}

Наблюдение за несколькими элементами

const observer = new ResizeObserver(entries => {
  entries.forEach(({ target, contentRect }) => {
    // target — элемент, который изменился
    console.log(target.id, contentRect.width);
  });
});

document.querySelectorAll('.panel').forEach(panel => {
  observer.observe(panel);
});

Частые ошибки

  • Бесконечный цикл — если callback изменяет размер самого наблюдаемого элемента, это вызывает новое срабатывание. Браузер выбрасывает ResizeObserver loop limit exceeded. Избегайте синхронных изменений размера в callback.
  • Забыть disconnect — наблюдатель держит ссылку на элемент. В React-компонентах вызывайте disconnect в useEffect cleanup.
  • Путаница contentRect и borderBoxSizecontentRect без padding/border, borderBoxSize включает их. Выбирайте в зависимости от задачи.

Связанные темы

Ресурсы