← Назад к вопросам
Как получить доступ к размеру элемента?
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>
))
}