[css]对于“<video>若存在多个fixed定位的父元素,其三个点菜单不显示”的问题的探索
1、问题重现
有下面的 HTML 结构(没错,我说的就是 Element Plus 的 Dialog
组件):
/* css */
.div, .inner-div {
position: fixed;
inset: 0;
}
.video {
height: 500px;
}
<!-- html -->
<div class="div">
<div class="inner-div">
<video class="video" autoplay controls src="http://xxx.com/xxxx3.mp4"></video>
</div>
</div>
video 的两个父元素 .div
、.inner-div
都是 fixed
定位。一切看起来都没什么问题,直到你想点击 video 控件最右侧的三个点按钮,你会发现毫无反应。
2、video
的弹出菜单哪去了?
在 Chrome 开发者工具的设置中,勾选“显示用户代理 Shadow DOM”,再返回“元素”面板,就能看到 video
元素的内部样式。这时你会发现,点击三个点其实是有反应的,对应的元素在下图的红圈中:
为了节省点流量,图片经过压缩,有点模糊不清,请看官谅解。下同。
查看其样式,可以发现它的 position
计算结果非常奇怪:
经过尝试,position
的 top
、right
、bottom
的值不受外面的样式影响,它是浏览器内部计算出来的。也就是说,不管你在 video
的父容器上怎么折腾,出现异常的 top
、right
、bottom
的值永远是 539.33、1507.330、-144,至少在我的浏览器里是这样的(Chrome 125.0.6422.142)。
3、能否使用伪类选择器(-webkit-xxxx
)矫正错误的定位?
答案是不能。经过尝试,在 Shadow DOM 中,如果一个元素有 pseudo="-webkit-xxxx"
这样的属性,那么它就可以使用属性值作为伪类选择器,从而改变它的样式:
video::-webkit-xxxx { }
不幸的是,咱们的目标元素的 pseudo
属性的值为 -internal-media-controls-overflow-menu-list
,单词 internal 就说明了它是内部的,外部无法影响它。
4、Fixed
定位能从相对于窗口(视口,viewport
)改为相对于其他元素?
答案是可以。没想到吧.jpg!MDN 文档对此有详细的描述:
元素会被移出正常文档流,并不为元素预留空间,而是通过指定元素相对于屏幕视口(
viewport
)的位置来指定元素位置。元素的位置在屏幕滚动时不会改变。打印时,元素会出现在的每页的固定位置。fixed
属性会创建新的层叠上下文。当元素祖先的transform
、perspective
、filter
或backdrop-filter
属性非none
时,容器由视口改为该祖先。
真的没想到.jpg!video
元素的三个点弹出菜单就是 fixed
定位。
不过,值得指出的是,这个弹出菜单设置了相对于三个点图标的定位——锚点定位:
/* css 弹出菜单 */
position-anchor: --internal-media-control-button-anchor;
/* css 三个点图标 */
anchor-name: --internal-media-control-button-anchor;
这指定了弹出菜单相对三个点的图标来确定位置。不过这不影响本文的结论。
5、如何利用上面的特点?
Chrome 是如何计算出上面第 2 点那些 top
、right
、bottom
值的不得而知,因为这是浏览器内部实现的。既然这个弹出菜单的位置能够被外部影响,那么我们可以尝试利用上面第 4 点提到的 fixed
定位的特性,看看能不能让这个弹出菜单相对一个咱们自己的元素来定位?
根据上面提到的 MDN 文档,让任意一个 video
的父元素的 transform
、perspective
、filter
或 backdrop-filter
属性值不为 none
,就会让 video
中的弹出菜单变成相对其进行定位。尝试给 .inner-div
设置 transform
:
.inner-div {
transform: translate(0, 0);
}
刷新页面,点击三个点图标,菜单仍旧不能显示出来。难道这个方法不起作用吗?改为给 .div
设置 transform
:
.div {
transform: translate(0, 0);
}
刷新页面,点击 video
的三个点图标,这次弹出菜单出来了!
经过多次尝试,发现了下面的规律:
- 如果
video
有多个fixed
定位的父元素,比如层次结构为div1 - div2 - div3 - video
,那么给最外层的div1
设置transform
、perspective
、filter
或backdrop-filter
才会起作用 - 除了
div1
, 给body
(包含) 到div1
之间的任意video
的父元素设置以上 4 个 css 属性都会起作用。
例如下面的代码:
body { perspective: 0; } /* 起作用 */
a { filter: blur(0); } /* 起作用 */
div { position: fixed; }
.div1 { transform: translate(0, 0); } /* 起作用 */
.div1 { filter: blur(0); } /* 起作用 */
.div1 { perspective: 0; } /* 起作用 */
.div1 { backdrop-filter: blur(0); } /* 起作用 */
.div2 { transform: translate(0, 0); } /* 不起作用 */
.div3 { transform: translate(0, 0); } /* 不起作用 */
<body>
<a>
<div class="div1">
<div class="div2"
<div class="div3"
<video src="xxxx.mp4" controls autoplay></video>
</div>
</div>
</div>
</a>
</body>
下图展示了 filter: blur(1px)
的作用。可以看到视频内容有些模糊,这是 blur
滤镜起的作用,而弹出菜单显示出来了:
视频内容为直播带货_0-O
6、需要注意的地方
最好使用除了 transform
之外的那 3 个 css 属性,因为 transform 是常用的 css 属性,可能会被其他样式覆盖,比如,如果其他地方为上面代码的 .div1
设置了 transform: translate(-50%, -50%)
,受此影响,video
的弹出菜单不会显示在正确的位置。
UPDATE: 2024-10-23
CSS 已经支持某些 transform 属性简写了,例如transition: -50%, -50%;
、scale: .5;
具体可查看 MDN 上的文档
7、另一种解决办法
这种办法原理特别简单:隐藏原生控件,自己实现一套控件。就是有点麻烦。
8、结束
如果哪位大佬阅读了 Chrome 相关源代码,知道其中的原理,请不吝赐教。期待 Chrome 修复此 Bug。
END
2025年10月新盘 做第一批吃螃蟹的人coinsrore.com
新车新盘 嘎嘎稳 嘎嘎靠谱coinsrore.com
新车首发,新的一年,只带想赚米的人coinsrore.com
新盘 上车集合 留下 我要发发 立马进裙coinsrore.com
做了几十年的项目 我总结了最好的一个盘(纯干货)coinsrore.com
新车上路,只带前10个人coinsrore.com
新盘首开 新盘首开 征召客户!!!coinsrore.com
新项目准备上线,寻找志同道合的合作伙伴coinsrore.com
新车即将上线 真正的项目,期待你的参与coinsrore.com
新盘新项目,不再等待,现在就是最佳上车机会!coinsrore.com
新盘新盘 这个月刚上新盘 新车第一个吃螃蟹!coinsrore.com
新车新盘 嘎嘎稳 嘎嘎靠谱
一般人遇不到这么奇葩的事情……
☹☹