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

Chrome扩展开发:如何拦截YouTube缩略图更新并实时替换内容?

解决YouTube SPA导航后缩略图时长替换内容不更新的问题

我正在开发一款Chrome扩展,目标是把YouTube推荐视频缩略图上的时长(比如14:32)替换成对应的频道名称(比如PewDiePie)。单次替换功能已经实现,但遇到一个问题:当通过点击推荐视频导航到新页面时,缩略图已经切换到新视频,但显示的还是旧的频道名称,内容没有更新。

问题根源

YouTube是单页应用(SPA),页面导航时不会完全刷新DOM,而是复用现有节点。原来的MutationObserver只监听了新增节点(如下滑加载新内容),但对于已经存在的缩略图节点,当它们关联的视频信息更新时,不会触发新增节点的监听逻辑,导致旧的频道名残留。

解决方案

需要从三个方面调整代码:

  1. 优化频道名的获取逻辑,确保能稳定获取当前缩略图对应的最新频道名
  2. 扩展MutationObserver的监听范围,覆盖现有节点的属性、内容变化
  3. 监听SPA导航的URL变化,触发全局重新更新所有缩略图

修改后的代码

TypeScript版本

function processNode(node: HTMLElement) {
    // 从缩略图所在的视频卡片节点中获取频道名,层级更稳定
    const videoCard = node.closest('ytd-video-renderer');
    if (!videoCard) return;

    const channelName = videoCard.querySelector('ytd-channel-name #text');
    if (channelName?.textContent) {
        node.textContent = channelName.textContent;
    }
}

function replaceAllThumbnailTimes(): void {
    document.querySelectorAll('span.ytd-thumbnail-overlay-time-status-renderer#text')
        .forEach(node => processNode(node as HTMLElement));
}

function setupMutationObserver(): void {
    const observer = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
            // 处理新增节点
            for (const node of mutation.addedNodes) {
                if (node instanceof HTMLElement && 
                    node.matches('span.ytd-thumbnail-overlay-time-status-renderer#text')) {
                    processNode(node);
                }
            }

            // 处理现有节点的属性/内容变化:检查是否是时间节点或其父节点变化
            if (mutation.target instanceof HTMLElement) {
                // 如果变化的是时间节点本身,直接处理
                if (mutation.target.matches('span.ytd-thumbnail-overlay-time-status-renderer#text')) {
                    processNode(mutation.target);
                } 
                // 如果变化的是视频卡片或频道名元素,找到其内部的时间节点处理
                else if (mutation.target.closest('ytd-video-renderer')) {
                    const timeNode = mutation.target.closest('ytd-video-renderer')?.querySelector('span.ytd-thumbnail-overlay-time-status-renderer#text');
                    if (timeNode instanceof HTMLElement) {
                        processNode(timeNode);
                    }
                }
            }
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true,
        characterData: true,
        attributes: true,
        attributeFilter: ['href', 'title'] // 监听关键属性变化,减少不必要触发
    });
}

function setupNavigationListener(): void {
    // 监听SPA导航的URL变化
    let currentUrl = window.location.href;
    setInterval(() => {
        const newUrl = window.location.href;
        if (newUrl !== currentUrl) {
            currentUrl = newUrl;
            // 延迟一点执行,确保页面内容更新完成
            setTimeout(replaceAllThumbnailTimes, 500);
        }
    }, 200);
}

// 初始化执行
replaceAllThumbnailTimes();
setupMutationObserver();
setupNavigationListener();

JavaScript版本(可直接在控制台运行)

function processNode(node) {
    const videoCard = node.closest('ytd-video-renderer');
    if (!videoCard) return;

    const channelName = videoCard.querySelector('ytd-channel-name #text');
    if (channelName?.textContent) {
        node.textContent = channelName.textContent;
    }
}

function replaceAllThumbnailTimes() {
    document.querySelectorAll('span.ytd-thumbnail-overlay-time-status-renderer#text')
        .forEach(node => processNode(node));
}

function setupMutationObserver() {
    const observer = new MutationObserver((mutations) => {
        for (const mutation of mutations) {
            for (const node of mutation.addedNodes) {
                if (node instanceof HTMLElement && 
                    node.matches('span.ytd-thumbnail-overlay-time-status-renderer#text')) {
                    processNode(node);
                }
            }

            if (mutation.target instanceof HTMLElement) {
                if (mutation.target.matches('span.ytd-thumbnail-overlay-time-status-renderer#text')) {
                    processNode(mutation.target);
                } else if (mutation.target.closest('ytd-video-renderer')) {
                    const timeNode = mutation.target.closest('ytd-video-renderer')?.querySelector('span.ytd-thumbnail-overlay-time-status-renderer#text');
                    if (timeNode instanceof HTMLElement) {
                        processNode(timeNode);
                    }
                }
            }
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true,
        characterData: true,
        attributes: true,
        attributeFilter: ['href', 'title']
    });
}

function setupNavigationListener() {
    let currentUrl = window.location.href;
    setInterval(() => {
        const newUrl = window.location.href;
        if (newUrl !== currentUrl) {
            currentUrl = newUrl;
            setTimeout(replaceAllThumbnailTimes, 500);
        }
    }, 200);
}

replaceAllThumbnailTimes();
setupMutationObserver();
setupNavigationListener();

关键改进点

  • 稳定的选择器:通过ytd-video-renderer层级获取频道名,避免依赖不稳定的父元素层级,适配YouTube DOM结构变化。
  • 扩展Observer监听:不仅处理新增节点,还监听现有节点的属性和内容变化,当视频卡片或时间节点本身变化时重新处理。
  • SPA导航监听:通过定时检查URL变化,触发全局更新,确保页面切换后所有缩略图都能同步最新内容。

内容的提问来源于stack exchange,提问作者dcferreira

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 10:20:25