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

滚动后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
修复方案

问题根源:

  1. 按钮挂载在body上用绝对定位,Facebook滚动加载时页面布局动态调整,导致按钮位置偏移
  2. setInterval轮询效率低,无法精准捕捉新视频加载时机

具体修复步骤

  1. 将按钮挂载到视频父容器:设置父容器为相对定位,按钮用绝对定位绑定到视频,跟随视频同步移动
  2. 用MutationObserver替代轮询:实时监听DOM变化,精准捕捉新加载的视频元素
  3. 优化位置计算:基于视频在父容器内的相对位置布局按钮,无需依赖全局偏移量

修复后的完整代码

// ==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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 20:57:03