为何position: absolute会影响getBoundingClientRect()的返回结果?
我正在开发自定义视频控制组件,其中包含支持跳转的进度条,实现思路如下:
- 使用
getBoundingClientRect()方法获取进度条容器的边界坐标; - 通过鼠标事件的
e.clientX获取点击位置; - 利用两者的差值计算进度条的宽度。
但实际操作中发现getBoundingClientRect()的返回值存在问题:我理解该方法与e.clientX的坐标均相对视口,通过简单减法即可确定点击位置,但实际返回的x坐标远小于预期,甚至最左侧元素的x坐标为负数。
我猜测是position: absolute导致的问题。将包裹视频和控制栏的容器.player的position设为relative后,其宽度从视频宽度变为占满整个body,但getBoundingClientRect()返回的x坐标恢复正常,进度条功能也能正常工作。
因此我想了解:为何position: absolute会影响getBoundingClientRect()的返回结果?还是有其他因素导致的?
测试浏览器为Firefox和Chrome,代码如下:
const media = document.querySelector('video'); const controls = document.querySelector('.controls'); const timerContainer = document.querySelector('.timer'); const timer = document.querySelector('.timer span'); const timerBar = document.querySelector('.timer div'); // Make a seek bar const timerBarBound = timerContainer.getBoundingClientRect(); console.log(timerBarBound); timerContainer.addEventListener('click', (e) => { console.log(e.clientX); let clickProcess = e.clientX - timerBarBound.x; if (clickProcess > 0 && clickProcess < timerBarBound.width) { let barProcess = timerContainer.clientWidth * (clickProcess / timerBarBound.width); timerBar.style.width = `${barProcess}px`; } });
.player { position: absolute; border: 1px solid red; } .controls { position: absolute; width: 400px; left: 50%; margin-left: -200px; display: flex; } button:before { position: relative; content: attr(data-operate); } button { position: relative; height: 38px; line-height: 19px; flex: 1; } .timer { line-height: 38px; height: 38px; flex: 5; position: relative; } .timer div { position: absolute; left: 0; top: 0; width: 0; height: 38px; z-index: 2; } .timer span { position: absolute; z-index: 3; left: 19px; }
<body> <div class="player"> <video> <source src="video.mp4" type="video/mp4"> <source src="video.webm" type="video/webm"> </video> <div class="controls"> <button class="play" data-operate="P"></button> <button class="stop" data-operate="S"></button> <div class="timer"><div></div><span>00:00 / 02:00</span></div> <button class="rwd" data-operate="B"></button> <button class="fwd" data-operate="F"></button> </div> </div> <script src="scripts/custom-player.js"></script> </body>
解答
getBoundingClientRect()的返回值本身和元素的position属性没有直接关联——它始终返回元素相对于视口左上角的坐标,不管元素是absolute、relative还是static。你遇到的问题是两个因素共同作用的结果:
1. 绝对定位元素的收缩特性
当.player设为position: absolute时,它的宽度默认会收缩到内容宽度(也就是视频的宽度)。而你的.controls设置了width:400px且通过left:50%; margin-left:-200px做水平居中——这里的居中是相对于.player的,不是视口。
这就导致:.controls(包含进度条容器.timer)的实际位置可能超出了.player的宽度范围,甚至一部分跑到视口左侧外面去了——此时getBoundingClientRect()返回的x坐标自然会是负数,因为元素的左边缘在视口左侧之外。
2. 代码中的时机问题
你在页面加载时就执行了const timerBarBound = timerContainer.getBoundingClientRect();,此时视频可能还没加载完成,.player的宽度还没确定,获取到的边界值是不准确的。当你把.player改成position: relative后,它的宽度会占满body,.controls的居中是相对于body的,不会超出视口,同时视频加载完成后元素布局稳定,获取的坐标就正常了。
修复建议
- 不要在页面初始化时就获取边界值,应该在点击事件内部实时获取:
timerContainer.addEventListener('click', (e) => { const timerBarBound = timerContainer.getBoundingClientRect(); // 实时获取 let clickProcess = e.clientX - timerBarBound.left; // 用left代替x,兼容性更好 if (clickProcess > 0 && clickProcess < timerBarBound.width) { let barProcess = timerContainer.clientWidth * (clickProcess / timerBarBound.width); timerBar.style.width = `${barProcess}px`; } });
- 如果要保留
.player的position: absolute,需要给它设置明确的宽度,比如和视频宽度一致,或者设置width:100%,确保.controls的居中不会超出视口范围。
内容的提问来源于stack exchange,提问作者Yika

