滚动后HTML元素定位错误:Facebook视频调速按钮偏移修复求助
问题描述
我正在编写Tampermonkey脚本,给Facebook视频添加调速按钮。脚本会获取页面所有video元素,根据位置放置按钮,初始运行正常,但滚动加载更多视频后,按钮位置和视频不匹配,部分视频还没有调速按钮。怀疑是页面顶部位置计算出错,附上代码和前后效果截图,求修复方法。
滚动前效果:
滚动后效果:
原脚本代码:
function addButton(){ function getOffset( el ) {//get the position of the element respective to the top and the left of the page var _x = 0; var _y = 0; while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) { _x += el.offsetLeft - el.scrollLeft; _y += el.offsetTop - el.scrollTop; el = el.offsetParent; } return { top: _y, left: _x }; } function setButton(video){//add buttons next to the videos var elDistanceToTop = window.pageYOffset + video.getBoundingClientRect().top var y=elDistanceToTop;//get the distance from the element to the TOP of the page //var y = getOffset( video ).top; var x=getOffset( video ).left;//get the distance from the element to the LEFT of the page var button1 = document.createElement('button'); button1.textContent = '1x'; // Set button position button1.style.position = 'absolute'; button1.style.left = x+video.getBoundingClientRect().width+ 'px';//place button to the left of video button1.style.top = y+ 'px';//position from the top of the page to place the button button1.style.width='58px'; button1.style.height=video.getBoundingClientRect().height/3+'px'; button1.addEventListener('click', function () { // Increase speed by 1, maximum speed is 4.0 video.playbackRate = 1; }); // Add button to the document body document.body.appendChild(button1); var button2 = document.createElement('button'); button2.textContent = '1.5x'; // Set button position button2.style.position = 'absolute'; button2.style.left = x+video.getBoundingClientRect().width+ 'px'; button2.style.top = y+video.getBoundingClientRect().height/3+ 'px'; button2.style.width='58px'; button2.style.height=video.getBoundingClientRect().height/3+'px'; button2.addEventListener('click', function () { // Increase speed by 1.5, maximum speed is 4.0 video.playbackRate = 1.5; }); // Add button to the document body document.body.appendChild(button2); var button3 = document.createElement('button'); button3.textContent = '2x'; // Set button position button3.style.position = 'absolute'; button3.style.left = x+video.getBoundingClientRect().width+ 'px'; button3.style.top = y+video.getBoundingClientRect().height*2/3+ 'px'; button3.style.width='58px'; button3.style.height=video.getBoundingClientRect().height/3+'px'; button3.addEventListener('click', function () { // Increase speed by 2, maximum speed is 4.0 video.playbackRate = 2; }); // Add button to the document body document.body.appendChild(button3); } const videoElement = document.querySelectorAll('video'); videoElement.forEach((video, index) => { //check to see if the video had had buttons const loadedBefore = video.getAttribute('data-loaded') === 'true'; if (loadedBefore) {//if it has, do nothing } else {//if it doesn't have buttons, add them setButton(video); } }); videoElement.forEach((video, index) => {//mark all exsisting video on the page as button-having, //so as not to add button to them again video.setAttribute('data-loaded', 'true'); }); } setInterval(addButton, 3000);//the loop to check and add button again and again
修复方案
问题根源:
- 按钮挂载在
body上用绝对定位,Facebook滚动加载时页面布局动态调整,导致按钮位置偏移 setInterval轮询效率低,无法精准捕捉新视频加载时机
具体修复步骤
- 将按钮挂载到视频父容器:设置父容器为相对定位,按钮用绝对定位绑定到视频,跟随视频同步移动
- 用MutationObserver替代轮询:实时监听DOM变化,精准捕捉新加载的视频元素
- 优化位置计算:基于视频在父容器内的相对位置布局按钮,无需依赖全局偏移量
修复后的完整代码
// ==UserScript== // @name Facebook Video Speed Controller // @namespace http://tampermonkey.net/ // @version 0.1 // @description Add speed control buttons to Facebook videos // @author You // @match https://www.facebook.com/* // @grant none // ==/UserScript== (function() { 'use strict'; // 创建调速按钮组 function createSpeedButtons(video) { // 确保视频父容器为定位元素 const parent = video.parentElement; if (getComputedStyle(parent).position === 'static') { parent.style.position = 'relative'; } // 按钮容器,统一管理按钮布局 const buttonContainer = document.createElement('div'); buttonContainer.style.position = 'absolute'; buttonContainer.style.right = '0'; buttonContainer.style.top = '50%'; buttonContainer.style.transform = 'translateY(-50%)'; buttonContainer.style.display = 'flex'; buttonContainer.style.flexDirection = 'column'; buttonContainer.style.gap = '2px'; buttonContainer.style.zIndex = '9999'; // 定义调速档位 const speeds = [ { text: '1x', rate: 1.0 }, { text: '1.5x', rate: 1.5 }, { text: '2x', rate: 2.0 } ]; speeds.forEach(speed => { const button = document.createElement('button'); button.textContent = speed.text; button.style.padding = '8px 12px'; button.style.border = 'none'; button.style.borderRadius = '4px'; button.style.backgroundColor = 'rgba(0,0,0,0.7)'; button.style.color = 'white'; button.style.cursor = 'pointer'; button.style.fontSize = '14px'; button.addEventListener('click', () => { video.playbackRate = speed.rate; }); buttonContainer.appendChild(button); }); // 将按钮容器添加到视频父容器 parent.appendChild(buttonContainer); // 标记视频已处理,避免重复添加 video.setAttribute('data-speed-buttons-added', 'true'); } // 处理页面已有的视频 function processExistingVideos() { document.querySelectorAll('video:not([data-speed-buttons-added])').forEach(video => { createSpeedButtons(video); }); } // 监听页面新增的视频元素 function setupMutationObserver() { const observer = new MutationObserver((mutations) => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { // 直接处理新增的video元素 if (node.tagName === 'VIDEO' && !node.getAttribute('data-speed-buttons-added')) { createSpeedButtons(node); } // 处理包含video的容器元素 else if (node.nodeType === Node.ELEMENT_NODE) { const videos = node.querySelectorAll('video:not([data-speed-buttons-added])'); videos.forEach(video => createSpeedButtons(video)); } }); }); }); // 监听body下所有DOM变化 observer.observe(document.body, { childList: true, subtree: true }); } // 初始化执行 processExistingVideos(); setupMutationObserver(); })();
修复说明
- 按钮跟随视频:按钮容器绑定到视频父容器,滚动或布局调整时按钮始终和视频保持相对位置
- 精准高效:MutationObserver实时捕捉新视频,避免无效轮询,性能更优
- 样式优化:半透明按钮不遮挡视频内容,布局更合理
内容的提问来源于stack exchange,提问作者Randy VU
相关产品推荐
相关产品推荐

