Измерение DOM

Haunted — это библиотека для создания веб-компонентов на базе стандартов Web Components и Hooks в стиле React. Она позволяет создавать реактивные пользовательские элементы с минимальной обвязкой. Одной из важных задач при работе с компонентами является измерение DOM-элементов для управления их размером, положением и поведением в интерфейсе.

Реактивные хуки и доступ к DOM

Haunted использует хуки, похожие на React. Для работы с DOM внутри компонента применяются хуки useRef и useEffect.

import { html, component } from 'haunted';
import { useRef, useEffect, useState } from 'haunted';

function MyComponent() {
  const elementRef = useRef(null);
  const [dimensions, setDimensions] = useState({ width: 0, height: 0 });

  useEffect(() => {
    const el = elementRef.current;
    if (el) {
      const { width, height } = el.getBoundingClientRect();
      setDimensions({ width, height });
    }
  }, []);

  return html`
    <div ref=${elementRef} style="background: lightgray;">
      Ширина: ${dimensions.width}px, Высота: ${dimensions.height}px
    </div>
  `;
}

customElements.define('my-component', component(MyComponent));

Ключевые моменты:

  • useRef создаёт ссылку на DOM-элемент.
  • useEffect выполняется после первого рендера компонента, что позволяет безопасно обращаться к DOM.
  • getBoundingClientRect() возвращает размеры и координаты элемента.

Измерение динамических изменений

Для отслеживания изменения размера элемента удобно использовать ResizeObserver. Он позволяет реагировать на изменение размеров без постоянного опроса DOM.

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

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

  observer.observe(el);

  return () => observer.disconnect();
}, []);

Особенности использования:

  • ResizeObserver асинхронно уведомляет о любых изменениях размеров.
  • В useEffect обязательно возвращать функцию очистки, чтобы избежать утечек памяти.

Работа с позиционированием

Иногда требуется не только размеры, но и положение элемента на странице. Для этого используют getBoundingClientRect():

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

  const updatePosition = () => {
    const { top, left } = el.getBoundingClientRect();
    setPosition({ top, left });
  };

  updatePosition();
  window.addEventListener('scroll', updatePosition);
  window.addEventListener('resize', updatePosition);

  return () => {
    window.removeEventListener('scroll', updatePosition);
    window.removeEventListener('resize', updatePosition);
  };
}, []);

Примечания:

  • В useEffect добавляется обработка событий scroll и resize.
  • Функция очистки удаляет слушателей, предотвращая утечки.

Комбинирование с CSS-переменными

Измеренные значения можно использовать для динамического изменения стилей через CSS-переменные:

return html`
  <div
    ref=${elementRef}
    style="--width: ${dimensions.width}px; --height: ${dimensions.height}px;"
  >
    Контент
  </div>
`;

Это позволяет создавать адаптивные компоненты без прямого изменения стилей через JS каждый раз.

Оптимизация производительности

  • useRef не вызывает повторный рендер при изменении ссылки.
  • ResizeObserver эффективнее setInterval для отслеживания размеров.
  • Измерения DOM лучше производить после рендера или в requestAnimationFrame, чтобы минимизировать layout thrashing.

Практическая структура компонента с измерением DOM

  1. Создаётся ref для элемента.
  2. Инициализируется состояние для хранения размеров и позиции.
  3. В useEffect выполняется первое измерение.
  4. Подключается ResizeObserver для динамических изменений.
  5. Опционально — добавляются обработчики scroll и resize.
  6. Состояние используется для рендера или передачи в стили.

Такой подход обеспечивает реактивность компонента и позволяет гибко управлять его визуальным поведением в зависимости от размеров и позиции на странице.