React useRef在useEffect中仍未设置,图片动态定位失效
问题说明
尝试通过useRef获取DOM元素的动态尺寸,用来将图片显示在随机位置,但初始获取useRef.current时返回undefined,推测是目标div尚未加载完成。用useEffect等待元素加载后,发现useRef.current的尺寸始终为0,导致图片无法正常移动。
原代码
import './MouseAim.css' import '../Game.css' import React, {useEffect, useRef, useState} from 'react' import TargetImg from '../../../assets/Target.png' import _ from "lodash"; function MouseAim() { const [start, setStart] = useState(true) const [target, setTarget] = useState(true) const elementDimensions = useRef() //wait for elementDimensions to be set useEffect(() => { console.log('elementDimensions', elementDimensions.current?.clientHeight) } , [elementDimensions]) return ( <div> <div className="main-container"> { start ? <div ref={elementDimensions} className="aim-container"> { target ? <input className="target" type="image" style={{position: "relative", left:elementDimensions.current?.clientHeight+"px" , top:_.random(0, elementDimensions.current?.clientHeight)+"px"}} onClick={() => console.log("hello")} src={TargetImg} alt="target" /> :null } </div> : <div className="start-container"> <input className="start-button" type="button" value="Start Game" onClick={() => setStart(true)}/> </div> } </div> </div> ) } export default MouseAim
问题原因及修复方案
核心问题
- useRef不触发重渲染:
elementDimensions是ref对象,其current属性变化不会触发组件重渲染,所以依赖elementDimensions的useEffect无法响应元素尺寸的真实变化。 - 尺寸计算时机错误:JSX渲染时直接读取
elementDimensions.current?.clientHeight,初始阶段元素可能未完成CSS布局,导致值为0或undefined,且后续尺寸更新无法同步到样式。 - 定位方式不合理:使用
relative定位时,left/top是相对于元素自身初始位置偏移,而非容器范围,导致位置计算逻辑错误。
修复代码
import './MouseAim.css' import '../Game.css' import React, { useEffect, useRef, useState } from 'react' import TargetImg from '../../../assets/Target.png' import _ from "lodash"; function MouseAim() { const [start, setStart] = useState(true) const [target, setTarget] = useState(true) const containerRef = useRef() const [containerSize, setContainerSize] = useState({ width: 0, height: 0 }) const [targetPosition, setTargetPosition] = useState({ left: 0, top: 0 }) // 监听容器尺寸变化,更新目标位置 useEffect(() => { const updateContainerInfo = () => { if (!containerRef.current) return const { clientWidth, clientHeight } = containerRef.current setContainerSize({ width: clientWidth, height: clientHeight }) // 计算目标位置时减去图片自身尺寸,避免超出容器(假设图片为50x50,可根据实际调整) setTargetPosition({ left: _.random(0, clientWidth - 50), top: _.random(0, clientHeight - 50) }) } // 初始加载时获取尺寸 updateContainerInfo() // 监听窗口大小变化 window.addEventListener('resize', updateContainerInfo) // 监听容器自身尺寸变化(比如CSS加载完成、容器内容变更) const resizeObserver = new ResizeObserver(updateContainerInfo) resizeObserver.observe(containerRef.current) // 清理监听 return () => { window.removeEventListener('resize', updateContainerInfo) resizeObserver.disconnect() } }, []) // 点击目标后重新随机位置 const handleTargetClick = () => { const { width, height } = containerSize setTargetPosition({ left: _.random(0, width - 50), top: _.random(0, height - 50) }) } return ( <div> <div className="main-container"> {start ? ( <div ref={containerRef} className="aim-container" style={{ position: 'relative' }}> {target && ( <input className="target" type="image" style={{ position: "absolute", left: `${targetPosition.left}px`, top: `${targetPosition.top}px` }} onClick={handleTargetClick} src={TargetImg} alt="target" /> )} </div> ) : ( <div className="start-container"> <input className="start-button" type="button" value="Start Game" onClick={() => setStart(true)} /> </div> )} </div> </div> ) } export default MouseAim
关键修改说明
- 用
containerSize状态存储容器尺寸,确保尺寸变化能触发组件重渲染,同步更新目标位置。 - 新增
ResizeObserver和窗口resize事件监听,覆盖容器尺寸变化的所有场景。 - 目标图片改用
absolute定位,配合父容器的relative定位,实现基于容器范围的位置计算。 - 点击目标时重新生成随机位置,增强交互逻辑。
- 计算随机位置时减去图片自身尺寸,避免图片超出容器可视范围。
内容的提问来源于stack exchange,提问作者Thavas Antonio
相关产品推荐
相关产品推荐

