
用户脚本在 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),是编写高鲁棒性用户脚本的必备范式。