
Chrome 125 中 HTML 原生控制栏的三点菜单(⋯)会错误地统一锚定在最后一个视频元素上,而非触发它的目标视频;该问题已在 Chrome 126 正式版修复,但针对仍需支持 Chrome 125 及更早版本的项目,需通过 CSS 重置或自定义控件规避。
这是一个典型的浏览器渲染层 Bug,而非 React 或 DOM 结构问题。从你提供的代码可见:key、id、controls 属性均已正确设置,且每个 <video></video> 独立封装在带唯一 ID 的 <div> 中——这排除了 React 列表渲染或事件委托导致的 DOM 复用干扰。问题根源在于 Chrome 125 的媒体控件(Media Controls UI)内部逻辑缺陷:其三点菜单的定位计算未正确绑定到触发事件的 <code><video></video> 实例,而是取用了页面中最后一个 controls 启用的 <video></video> 元素的几何边界。
✅ 验证方式:
在 Chrome 125 中打开 DevTools → 播放任意一个视频 → 点击其右下角三点菜单 → 执行以下命令观察:
document.querySelector('video[controls]').getBoundingClientRect()
你会发现返回的坐标始终对应最后一个 <video></video>,而非当前点击的目标。
? 临时解决方案(推荐):
方案一:禁用原生控件 + 自实现轻量控制栏(最稳定)
移除 controls 属性,用 ref 精确控制播放/暂停,并通过 position: absolute + getBoundingClientRect() 动态定位自定义菜单:
import React, { useRef, useState, useEffect } from 'react';
const VideoPlayer = ({ src, id }: { src: string; id: number }) => {
const videoRef = useRef<htmlvideoelement>(null);
const [showMenu, setShowMenu] = useState(false);
const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
const handleMenuClick = (e: React.MouseEvent) => {
e.stopPropagation();
if (!videoRef.current) return;
const rect = videoRef.current.getBoundingClientRect();
setMenuPosition({
top: rect.bottom + window.scrollY,
left: rect.right - 120 + window.scrollX // 菜单宽约120px,右对齐
});
setShowMenu(true);
};
return (
<div id="{`videodiv-${id}`}" classname="relative">
<video ref="{videoRef}" classname="w-[230px] h-[250px] object-cover rounded-lg" src="%7Bsrc%7D" controls></video>
{/* 自定义三点按钮 */}
<button onclick="{handleMenuClick}" classname="absolute bottom-2 right-2 w-6 h-6 bg-black/50 rounded-full flex items-center justify-center text-white text-xs" aria-label="更多选项">
⋯
</button>
{/* 自定义菜单(仅在需要时渲染) */}
{showMenu && (
<div classname="absolute z-50 bg-gray-800 text-white rounded shadow-lg py-1 min-w-[120px]" style="{menuPosition}" onclick="{(e)"> e.stopPropagation()}
>
<div classname="px-3 py-2 hover:bg-gray-700 cursor-pointer">全屏</div>
<div classname="px-3 py-2 hover:bg-gray-700 cursor-pointer">下载</div>
<div classname="px-3 py-2 hover:bg-gray-700 cursor-pointer" onclick="{()"> setShowMenu(false)}>
关闭
</div>
</div>
)}
{/* 点击空白处关闭菜单 */}
{showMenu && (
<div classname="fixed inset-0 z-40" onclick="{()"> setShowMenu(false)}
/>
)}
</div>
);
};<p><strong>方案二:CSS 强制重置(适用于简单场景)</strong><br>
若必须保留原生控件,可尝试重置 Chrome 媒体控件的定位上下文(效果有限,仅作补充):</p>
<pre class="brush:php;toolbar:false;">/* 在全局 CSS 中添加 */
video::-webkit-media-controls-panel {
position: static !important;
}
video::-webkit-media-controls-overlay-panel {
position: static !important;
}
⚠️ 注意:此方法在 Chrome 125 中成功率较低,因 bug 位于底层渲染逻辑,CSS 无法覆盖。
? 关键注意事项:
- 不要依赖
event.target的offsetParent或getBoundingClientRect()在原生菜单触发时获取位置——此时菜单尚未渲染,DOM 中无对应节点; - 避免为所有
<video></video>统一使用position: relative包裹后强行绝对定位菜单,易引发层级和滚动偏移问题; - 生产环境建议检测用户 UA 并降级:
/Chrome\/125\./.test(navigator.userAgent),对匹配用户启用自定义控件; - 该问题不影响 Firefox / Safari / Edge,属 Chrome 特定版本回归 Bug(Chromium issue #1549212)。
综上,升级至 Chrome 126+ 是根本解法;短期兼容务必转向可控性更强的自定义媒体控件方案——它不仅解决定位异常,还带来更好的可访问性(a11y)、主题定制与分析埋点能力。











