
用户脚本在 Tampermonkey/Greasemonkey 中不生效,但在浏览器控制台手动执行却正常,根本原因在于脚本执行时机过早——YouTube 使用 SPA 架构异步渲染页面,#actions 等关键节点尚未挂载;需采用可靠机制等待目标元素就绪。
用户脚本在 tampermonkey/greasemonkey 中不生效,但在浏览器控制台手动执行却正常,根本原因在于脚本执行时机过早——youtube 使用 spa 架构异步渲染页面,`#actions` 等关键节点尚未挂载;需采用可靠机制等待目标元素就绪。
现代 YouTube 页面基于 React 或类似框架构建,其核心 UI(如视频操作栏 #actions、标题区 #title)并非初始 HTML 直出,而是通过 JavaScript 动态插入 DOM。因此,用户脚本若在 document.readyState === 'interactive' 或 'complete' 时立即执行(即默认 @run-at document-idle 行为),极大概率会因目标元素尚未存在而报错或静默失败——例如 document.getElementById('actions') 返回 null,后续 appendChild() 抛出 TypeError,但因未显式捕获,错误被忽略,仅表现为“无反应”。
你提供的原始脚本正是典型场景:
var actionsDiv = document.getElementById('actions'); // ✅ 此时通常为 null
actionsDiv.appendChild(newButton); // ❌ TypeError: Cannot read property 'appendChild' of null而控制台中手动运行成功,是因为此时页面早已加载完成,DOM 完全就绪。
✅ 推荐方案:使用 MutationObserver 精准监听目标元素
相比 setTimeout(不可靠、易受网络/设备性能影响)或轮询 setInterval(低效且难终止),MutationObserver 是现代前端等待动态节点的标准解法:它能高效响应 DOM 变化,并在目标元素首次出现时立即触发回调。
以下是适配 YouTube 的健壮实现:
// ==UserScript==
// @name YouTube Downloader (Fixed)
// @namespace https://github.com/yourname
// @version 0.2
// @description 在 YouTube 视频页添加一键下载按钮(支持动态加载)
// @author qweren
// @match https://www.youtube.com/watch*
// @grant none
// ==/UserScript==
(function () {
'use strict';
const createDownloadButton = () => {
const btn = document.createElement('button');
btn.id = 'yt-download-btn';
btn.textContent = '⬇️ 下载';
btn.style.cssText = `
margin-left: 8px;
padding: 6px 12px;
background: #ff3b30;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
line-height: 1;
`;
btn.addEventListener('click', () => {
alert('功能占位:此处集成 youtube-dl 或 stream capture 逻辑');
});
return btn;
};
// 核心:观察并注入按钮
const observer = new MutationObserver((mutations) => {
const actions = document.querySelector('#actions');
if (actions && !document.getElementById('yt-download-btn')) {
const btn = createDownloadButton();
// 插入到 action 按钮组末尾(如分享、保存等按钮之后)
const buttonGroup = actions.querySelector('.ytd-menu-renderer')?.parentElement;
if (buttonGroup) {
buttonGroup.appendChild(btn);
} else {
actions.appendChild(btn);
}
console.log('[YT Downloader] 下载按钮已注入');
observer.disconnect(); // ✅ 任务完成,停止监听
}
});
// 开始监听 body,捕获所有子节点变动(深度 true)
observer.observe(document.body, { childList: true, subtree: true });
})();⚠️ 关键注意事项
- 精准匹配 URL:将 @match 从 https://www.youtube.com/* 收窄为 https://www.youtube.com/watch*,避免在首页、搜索页等无 #actions 的页面执行无效逻辑,提升性能与稳定性。
- 防重复注入:通过 !document.getElementById('yt-download-btn') 判断按钮是否已存在,避免多次注入导致界面异常。
- 优雅降级:MutationObserver 在所有现代浏览器(Chrome 26+、Firefox 14+、Edge 12+)中均原生支持,无需 polyfill。
- 避免 setTimeout 硬编码延时:setTimeout(..., 100) 属于“碰运气”方案——网速慢、CPU 负载高时仍可能失败;而 MutationObserver 是事件驱动,毫秒级响应,真正可靠。
? 总结
用户脚本“控制台能跑,扩展不生效”的本质是 执行时序与 DOM 生命周期不匹配。解决之道不是增加随机延时,而是拥抱现代 Web API:用 MutationObserver 主动监听目标节点的诞生,实现“元素一出现,逻辑立刻执行”。该模式不仅适用于 YouTube,也适用于所有基于 React/Vue/Svelte 构建的单页应用(如 Twitter/X、Gmail、Notion),是编写高鲁棒性用户脚本的必备范式。










