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

为什么移动端 div 的 offsetHeight 始终返回相同高度

问题原因

核心问题和“移动端”没有本质关系,是你的useEffect加了空依赖数组,只会在组件首次挂载完成后执行一次,后续元素高度无论因为什么原因变动,都不会重新读取offsetHeight,变量里存的永远是挂载那一刻的高度快照。

移动端问题更明显,只是因为挂载后触发高度变化的场景远多于桌面端,和你提到的几类因素都有关联,常见诱因包括:

  • 动态视口变动:移动端浏览器地址栏收起/弹出、横竖屏切换、底部工具栏显隐都会改变实际可视区大小,响应式布局、用了视口单位的容器高度会跟着变,而这些操作基本都发生在组件挂载之后。
  • 资源加载滞后:视频、图片这类会撑开容器的资源,挂载时往往还没加载完成,容器高度只是占位高度;等资源加载完撑开容器时,读高度的effect早就执行结束了。移动端网络环境更复杂,这类延迟更常见。
  • 布局偏移:自定义WebFont加载完成后替换默认字体,可能导致文本折行数变化,进而改变容器高度;另外旧版移动内核里100vh的计算值包含隐藏的地址栏,浏览器UI切换时也会出现高度差。
  • effect执行时机的影响:useEffect本身是在浏览器首次绘制完成后异步执行的,如果挂载后播放器会动态初始化、修改样式,这个effect读到的本身就可能不是最终稳定高度。
可靠实现方案

用浏览器原生的ResizeObserver监听元素本身的尺寸变化即可。这个API就是专门为监听元素尺寸设计的,不管高度变化是窗口缩放、资源加载还是内部内容改动导致的,都能精准触发,性能远好于监听window.resize或者轮询。

代码示例

推荐用useLayoutEffect包裹监听逻辑:它会在DOM更新完成、浏览器绘制前同步执行,读取高度的时机更早,能避免高度更新导致的界面闪烁。完整TSX代码如下:

import { useRef, useLayoutEffect } from 'react';

// 保留你原有的ref命名
const playerWrapperReference = useRef<HTMLDivElement>(null);
const videoParentNode = useRef<Node | null>(null);
const videoOffsetHeight = useRef<number>(0);

useLayoutEffect(() => {
  const targetEl = playerWrapperReference.current;
  if (!targetEl) return;

  // 保存父节点
  videoParentNode.current = targetEl.parentNode;

  // 统一的高度更新方法
  const syncHeight = () => {
    videoOffsetHeight.current = targetEl.offsetHeight;
    // 如果需要用高度驱动视图更新,换成state存储即可:
    // setPlayerHeight(targetEl.offsetHeight)
  };

  // 先同步一次初始高度
  syncHeight();

  // 初始化尺寸观察器
  const observer = new ResizeObserver(syncHeight);
  observer.observe(targetEl);

  // 组件卸载时清理监听,避免内存泄漏
  return () => observer.disconnect();
}, []); // 仅挂载时绑定一次监听,空依赖即可

补充说明

  • 依赖数组仍然为空是正常逻辑:监听只需要在挂载时绑定一次,后续元素变动会由观察器主动触发回调,不需要反复绑定销毁。
  • 不需要额外监听window.resize、orientationchange事件:只要元素自身高度因为窗口变化发生了改变,ResizeObserver就会触发;如果窗口变了但元素高度没变,也不会执行多余逻辑。如果高度变化来自内部图片/视频加载(窗口尺寸不变),ResizeObserver也能正常捕获,这是window.resize做不到的。
  • 目前所有主流移动端浏览器都原生支持ResizeObserver,常规业务不需要额外兼容。如果需要支持非常老旧的设备,引入对应polyfill即可,不要写定时器轮询高度,性能损耗太高。
  • 如果业务对视频加载后的尺寸精度要求极高,可以额外在视频的loadedmetadata事件里手动调用一次syncHeight做双保险,正常情况下ResizeObserver已经能覆盖这个场景。
  • 如果高度异常是100vh兼容问题导致的,可以搭配CSS的dvh动态视口单位修复,监听逻辑不需要改动。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.22 06:45:04