Реалізація Resize Observer для адаптивних компонентів сайту

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

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

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Resize Observer для адаптивних компонентів сайту
Проста
~1 робочий день
Часті питання

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

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Впровадження Resize Observer для адаптивних компонентів веб-сайту

ResizeObserver стежить за змінами розміру конкретного DOM-елемента. Не viewport, а саме елемента. Це ключова відмінність від CSS media queries — тут адаптивність прив'язана до контейнера компонента, а не до ширини екрана.

Класичний випадок: компонент графіка займає то всю ширину сторінки, то третину. CSS media query цього не бачить. ResizeObserver — бачить.

Базове використання

const observer = new ResizeObserver((entries) => {
  for (const entry of entries) {
    // entry.contentRect — розміри контентної області
    // entry.borderBoxSize — розміри з урахуванням border
    // entry.contentBoxSize — без padding/border
    // entry.devicePixelContentBoxSize — у фізичних пікселях

    const { width, height } = entry.contentRect
    console.log(`${entry.target.id}: ${width}x${height}`)
  }
})

observer.observe(element)
observer.unobserve(element)
observer.disconnect()

Container Queries через JavaScript до нативної підтримки

Нативні CSS Container Queries (@container) підтримуються у сучасних браузерах, але для старих або для складної логіки — 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)

    // CSS-розмір залишається попереднім
    canvas.style.width = `${width}px`
    canvas.style.height = `${height}px`

    // Масштабуємо контекст для retina
    ctx.scale(dpr, dpr)

    draw(ctx, width, height)
  })

  observer.observe(canvas.parentElement ?? canvas)
  return () => observer.disconnect()
}

React Hook

function useResizeObserver<T extends HTMLElement = HTMLDivElement>(): [
  RefObject<T | null>,
  DOMRectReadOnly | null,
] {
  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]
}

// Адаптивний компонент на основі хука:
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} width={rect?.width} />
      ) : (
        <FullChart data={data} width={rect?.width} height={rect?.height} />
      )}
    </div>
  )
}

Debounce для частих змін

ResizeObserver спрацьовує при кожній змінці розміру — при ресайзі вікна це може бути кілька разів у секунду. Для важких обчислень потрібен debounce:

function useResizeObserverDebounced<T extends HTMLElement>(
  delay = 150
): [RefObject<T | null>, DOMRectReadOnly | null] {
  const ref = useRef<T>(null)
  const [rect, setRect] = useState<DOMRectReadOnly | null>(null)
  const timerRef = useRef<ReturnType<typeof setTimeout>>()

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

    const observer = new ResizeObserver(([entry]) => {
      clearTimeout(timerRef.current)
      timerRef.current = setTimeout(() => setRect(entry.contentRect), delay)
    })

    observer.observe(el)
    return () => {
      observer.disconnect()
      clearTimeout(timerRef.current)
    }
  }, [delay])

  return [ref, rect]
}

Що включено

Настройка ResizeObserver для потрібних компонентів, React хуки з debounce, адаптація компонентів по ширині контейнера (а не viewport), автоматичний resize канваса при необхідності.

Терміни: 0,5 дня.