You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React窗口大小计算错误:Chrome设备旋转后尺寸不符的问题与修复

问题分析与修复方案

我之前在Chrome DevTools模拟设备旋转时也碰到过一模一样的问题,尺寸总是不准,咱们来一步步拆解原因和修复方案。

首先先贴出你的示例代码(方便上下文参考):

// ... imports
const ComponentName = () => {
  const [loading, setLoading] = useState(true);
  const [sizes, setSizes] = useState({ width: 0, height: 0, });
  const handleResize = useCallback((e) => {
    const ww = e === undefined ? window.innerWidth : e.target.innerWidth;
    const wh = e === undefined ? window.innerHeight : e.target.innerHeight;
    console.log("ww: ", ww);
    console.log("wh: ", wh);
    setSizes({ width: ww, height: wh });
    setLoading(false);
  }, []);
  useLayoutEffect(() => {
    window.addEventListener("resize", handleResize);
    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, [handleResize]);
  useEffect(() => {
    let mount = true;
    if (mount && loading) {
      handleResize();
    }
    return () => {
      mount = false;
    };
  }, [loading, handleResize]);
  console.log("sizes: ", sizes.width, " x ", sizes.height);
  return (
    <div>
      {/* ... render */}
    </div>
  );
};
export default ComponentName;

问题到底出在哪?

你碰到的这个情况,主要是Chrome DevTools模拟设备旋转时的时序问题加上代码里的几个小细节导致的:

  • resize事件触发太早:设备旋转时,浏览器得重新计算视口尺寸,但resize事件可能在浏览器还没完成视口调整就触发了,拿到的是旋转过程中的过渡尺寸,不是最终的正确值。
  • 没有防抖处理:旋转过程中可能会连续触发好几次resize事件,最后一次才是正确的尺寸,但你的代码每次触发都会更新状态,很可能留下最后一次错误的数值。
  • 只监听resize不够:移动端设备旋转时,会先触发orientationchange事件,之后才会完成视口调整并触发resize,只盯着resize可能跟不上这个节奏。
  • window.innerWidth的局限性:这个值有时候会被浏览器的UI(比如地址栏、底部导航栏)影响,旋转时这些UI的显示状态变化也会干扰尺寸计算,导致数值不准。

怎么修复?

针对上面的问题,我们可以对代码做几个关键调整:

1. 给resize事件加防抖

确保我们只在resize事件停止触发一段时间后,才去获取最终的尺寸,避开过渡状态的干扰。

2. 同时监听orientationchange事件

在设备旋转时,先通过orientationchange事件触发一次延迟的尺寸检测,确保拿到稳定后的视口数据。

3. 用visualViewport拿更准确的尺寸

window.visualViewport提供了实际可见的视口尺寸,不受浏览器UI变化的影响,更适合移动端场景。

4. 调整初始加载的尺寸获取时机

初始加载时稍微延迟一点再获取尺寸,确保视口已经稳定下来。

修复后的代码如下:

// ... imports
import { useState, useCallback, useLayoutEffect, useEffect } from 'react';

// 防抖函数:延迟执行回调,避免频繁触发
const debounce = (func, delay = 200) => {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  };
};

const ComponentName = () => {
  const [loading, setLoading] = useState(true);
  const [sizes, setSizes] = useState({ width: 0, height: 0 });

  // 调整尺寸获取逻辑,改用visualViewport
  const updateViewportSize = useCallback(() => {
    // 使用visualViewport的尺寸,更准确反映实际可见区域,兼容旧浏览器降级到innerWidth
    const ww = window.visualViewport?.width || window.innerWidth;
    const wh = window.visualViewport?.height || window.innerHeight;
    
    console.log("最终稳定尺寸: ", ww, " x ", wh);
    setSizes({ width: ww, height: wh });
    setLoading(false);
  }, []);

  // 给updateViewportSize加上防抖
  const debouncedUpdateSize = useCallback(debounce(updateViewportSize), [updateViewportSize]);

  useLayoutEffect(() => {
    // 同时监听resize和orientationchange事件
    window.addEventListener("resize", debouncedUpdateSize);
    window.addEventListener("orientationchange", debouncedUpdateSize);

    // 监听visualViewport自身的resize事件(如果支持的话)
    window.visualViewport?.addEventListener("resize", debouncedUpdateSize);

    return () => {
      // 清理所有监听事件
      window.removeEventListener("resize", debouncedUpdateSize);
      window.removeEventListener("orientationchange", debouncedUpdateSize);
      window.visualViewport?.removeEventListener("resize", debouncedUpdateSize);
    };
  }, [debouncedUpdateSize]);

  useEffect(() => {
    let mount = true;
    // 初始加载时延迟100ms再获取尺寸,确保视口稳定
    const timer = setTimeout(() => {
      if (mount && loading) {
        updateViewportSize();
      }
    }, 100);

    return () => {
      mount = false;
      clearTimeout(timer);
    };
  }, [loading, updateViewportSize]);

  console.log("当前状态尺寸: ", sizes.width, " x ", sizes.height);
  return (
    <div>
      {/* ... render */}
    </div>
  );
};
export default ComponentName;

额外小提示

  • 防抖的延迟时间(200ms)可以根据实际情况调整,一般100-300ms都能覆盖浏览器视口调整的时间。
  • 如果你需要兼容非常旧的浏览器,可以保留window.innerWidth作为降级方案,代码里已经做了兼容。
  • 在Chrome DevTools里测试时,旋转后稍等片刻,让防抖函数触发,就能拿到正确的尺寸了。

内容的提问来源于stack exchange,提问作者mmstarz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:56:49