← Назад к вопросам

Как получить доступ к размеру элемента?

2.3 Middle🔥 141 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Получение размера элемента в React

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

Метод 1: Использование ref и useEffect

Простой способ получить размер элемента при первом рендере:

function ComponentWithSize() {
  const elementRef = useRef<HTMLDivElement>(null)
  const [size, setSize] = useState({ width: 0, height: 0 })
  
  useEffect(() => {
    if (elementRef.current) {
      const { offsetWidth, offsetHeight } = elementRef.current
      setSize({
        width: offsetWidth,
        height: offsetHeight
      })
    }
  }, []) // Вызывается один раз при монтировании
  
  return (
    <>
      <div ref={elementRef} className="box">
        Размер этого элемента: {size.width}x{size.height}
      </div>
    </>
  )
}

Метод 2: ResizeObserver для отслеживания изменений

Когда нужно реагировать на изменение размера элемента:

function ComponentWithResizeObserver() {
  const elementRef = useRef<HTMLDivElement>(null)
  const [size, setSize] = useState({ width: 0, height: 0 })
  
  useEffect(() => {
    const element = elementRef.current
    if (!element) return
    
    // ResizeObserver вызывается при изменении размера
    const observer = new ResizeObserver(() => {
      setSize({
        width: element.offsetWidth,
        height: element.offsetHeight
      })
    })
    
    observer.observe(element)
    
    return () => observer.disconnect()
  }, [])
  
  return (
    <div ref={elementRef} className="resizable-box">
      Текущий размер: {size.width}x{size.height}
    </div>
  )
}

Метод 3: Кастомный хук useElementSize

Для переиспользуемости лучше создать кастомный хук:

interface ElementSize {
  width: number
  height: number
}

function useElementSize<T extends HTMLElement>(): [React.RefObject<T>, ElementSize] {
  const ref = useRef<T>(null)
  const [size, setSize] = useState<ElementSize>({ width: 0, height: 0 })
  
  useEffect(() => {
    const element = ref.current
    if (!element) return
    
    // Установка начального размера
    setSize({
      width: element.offsetWidth,
      height: element.offsetHeight
    })
    
    // Отслеживание изменений
    const observer = new ResizeObserver(() => {
      setSize({
        width: element.offsetWidth,
        height: element.offsetHeight
      })
    })
    
    observer.observe(element)
    return () => observer.disconnect()
  }, [])
  
  return [ref, size]
}

// Использование
function Card() {
  const [ref, size] = useElementSize<HTMLDivElement>()
  
  return (
    <div ref={ref} className="card">
      <p>Ширина: {size.width}px</p>
      <p>Высота: {size.height}px</p>
    </div>
  )
}

Метод 4: getBoundingClientRect для позиции и размера

Если нужны не только размеры, но и позиция элемента:

function ComponentWithBounds() {
  const elementRef = useRef<HTMLDivElement>(null)
  const [bounds, setBounds] = useState({
    width: 0,
    height: 0,
    top: 0,
    left: 0,
    x: 0,
    y: 0
  })
  
  const updateBounds = () => {
    if (elementRef.current) {
      const rect = elementRef.current.getBoundingClientRect()
      setBounds({
        width: Math.round(rect.width),
        height: Math.round(rect.height),
        top: Math.round(rect.top),
        left: Math.round(rect.left),
        x: Math.round(rect.x),
        y: Math.round(rect.y)
      })
    }
  }
  
  useEffect(() => {
    updateBounds()
    window.addEventListener(resize, updateBounds)
    window.addEventListener(scroll, updateBounds)
    
    return () => {
      window.removeEventListener(resize, updateBounds)
      window.removeEventListener(scroll, updateBounds)
    }
  }, [])
  
  return (
    <div ref={elementRef} className="element">
      <div>Позиция: ({bounds.x}, {bounds.y})</div>
      <div>Размер: {bounds.width} × {bounds.height}</div>
    </div>
  )
}

Практический пример: Адаптивный компонент

function ResponsiveGrid() {
  const containerRef = useRef<HTMLDivElement>(null)
  const [columns, setColumns] = useState(1)
  
  useEffect(() => {
    const container = containerRef.current
    if (!container) return
    
    const calculateColumns = () => {
      const width = container.offsetWidth
      // Каждая колонка минимум 300px
      return Math.max(1, Math.floor(width / 300))
    }
    
    setColumns(calculateColumns())
    
    const observer = new ResizeObserver(() => {
      setColumns(calculateColumns())
    })
    
    observer.observe(container)
    return () => observer.disconnect()
  }, [])
  
  return (
    <div
      ref={containerRef}
      style={{
        display: grid,
        gridTemplateColumns: `repeat(${columns}, 1fr)`,
        gap: 16px
      }}
    >
      {/* Элементы сетки */}
      {Array.from({ length: 12 }).map((_, i) => (
        <div key={i} className="grid-item">Элемент {i + 1}</div>
      ))}
    </div>
  )
}

Свойства для получения размера

  • offsetWidth / offsetHeight - размер с padding и border
  • clientWidth / clientHeight - размер с padding, без border
  • scrollWidth / scrollHeight - полный размер с прокруткой
  • getBoundingClientRect() - размер относительно viewport

Производительность

Избегай частого чтения размеров в render цикле - это вызывает reflow:

// Плохо - вызывает reflow в каждом элементе
function BadList({ items }) {
  return items.map(item => (
    <div key={item.id}>
      {item.ref.current?.offsetWidth} {/* Плохо! */}
    </div>
  ))
}

// Хорошо - размер вычисляется один раз
function GoodList({ items }) {
  const [sizes, setSizes] = useState({})
  
  useEffect(() => {
    const newSizes = items.reduce((acc, item) => {
      acc[item.id] = item.ref.current?.offsetWidth || 0
      return acc
    }, {})
    setSizes(newSizes)
  }, [items])
  
  return items.map(item => (
    <div key={item.id}>{sizes[item.id]}</div>
  ))
}
Как получить доступ к размеру элемента? | PrepBro