React:屏幕旋转后触发重渲染与offsetWidth重新计算
Hey Christoph, let's fix that frustrating orientation resize issue without forcing a full page reload—we can keep your app's temporary state intact while getting those image scales updated correctly.
The Root of the Problem
When the screen rotates, your orientationChange hook fires immediately, but the browser hasn't finished recalculating the DOM's layout yet. That means when you grab offsetWidth and offsetHeight right away, you're still getting the old, pre-rotation values. No wonder the images get truncated!
Solution 1: Wait for the DOM to Update with requestAnimationFrame
We can delay our scale calculation until the browser has finished updating the layout. requestAnimationFrame ensures our code runs right before the next repaint, which is perfect for grabbing fresh DOM dimensions:
const [scale, setScale] = useState(1) const wrapperRef = useRef(null) const box = useElementSize(wrapperRef) const orientationChange = useDeviceLandscapeOrientation() useLayoutEffect(() => { const updateScale = () => { const wrapperNode = wrapperRef.current if (!wrapperNode) return const verticalScale = wrapperNode.offsetWidth / width const horizontalScale = wrapperNode.offsetHeight / height setScale(Math.min(verticalScale, horizontalScale)) } // Wait for the browser to finish layout updates before calculating const frameId = requestAnimationFrame(updateScale) return () => cancelAnimationFrame(frameId) }, [box, width, height, orientationChange])
Solution 2: Use ResizeObserver for Reliable Size Tracking
Even better, ditch the orientation-specific hook entirely and use the browser's built-in ResizeObserver API. It listens directly for changes to your wrapper element's size—whether from screen rotation, window resizing, or even parent layout shifts. This is more robust than relying on orientation events alone:
const [scale, setScale] = useState(1) const wrapperRef = useRef(null) useEffect(() => { const wrapperNode = wrapperRef.current if (!wrapperNode) return const resizeObserver = new ResizeObserver((entries) => { for (const entry of entries) { const { width: wrapperWidth, height: wrapperHeight } = entry.contentRect const verticalScale = wrapperWidth / width const horizontalScale = wrapperHeight / height setScale(Math.min(verticalScale, horizontalScale)) } }) resizeObserver.observe(wrapperNode) return () => resizeObserver.unobserve(wrapperNode) }, [width, height])
Why These Work
requestAnimationFramewaits until the browser has completed post-rotation layout calculations, so you get accurateoffsetWidth/offsetHeightvalues.ResizeObservereliminates timing issues entirely—it triggers automatically whenever your wrapper's size changes, so you never have to guess when to update the scale.
Both approaches will trigger a component re-render with the correct scale values, no page reload required. The ResizeObserver method is my go-to here because it's more versatile and less prone to edge cases.
内容的提问来源于stack exchange,提问作者Christoph

