Tampermonkey脚本如何识别Ajax加载的Open Shadow-Root元素及设置运行时机?
我正在Chrome中编写一款Tampermonkey脚本,用于移除工作常用网站上特定class或title按钮的disabled属性。该网站大量使用Ajax加载内容,初始加载时主应用窗口为空,点击头部或侧边栏选项后,主窗口会通过Ajax加载内容。
经测试,脚本可成功识别头部和侧边栏中的按钮并修改其禁用状态,但主窗口中通过Ajax加载的按钮位于#shadow-root (open)内,脚本无法识别这些元素,即使手动运行脚本也无效。
当前脚本代码:
// ==Userscript== // @name Enable buttons // @match my.website.url.org // @require https://code.jquery.com/jquery-3.6.4.min.js // @require https://gist.github.com/raw/2625891/waitForKeyElements.js // @grant GM_addStyle // @version 0.1 // ==/Userscript== (function() { waitForKeyElements ('button.btn.ripple.primary', modifyButtons, false); function modifyButtons(jNode) { console.log ("~~~~~~~~ Button has been encountered ~~~~~~~~"); var buttons = document.querySelectorAll('button.btn.ripple.primary'); for (var i=0; i<buttons.length; i++) { buttons[i].disabled = false; console.log(i + " buttons touched and enabled."); } } })();
目标按钮的HTML结构:
#shadow-root (open) <!----> <button type="button" title="Create" class="btn ripple primary" disabled> <span>Create</span> </button> <!---->
需要解决两个问题:
- 如何让Tampermonkey脚本识别主窗口中通过Ajax加载的
#shadow-root (open)内的元素? - 针对Ajax驱动的网站,Tampermonkey的
Run at(document-start、document-body等)哪个设置更合适,无需手动运行脚本?
1. 识别Shadow DOM内的元素
普通DOM查询无法穿透Shadow DOM,必须先定位到Shadow根节点,再在其内部查找目标元素。结合Ajax动态加载的特性,推荐两种实现方式:
方式一:自定义轮询函数查找Shadow DOM元素
写一个循环检查的函数,遍历所有带Shadow根的元素,找到目标按钮后修改状态:
function waitForShadowElements(selector, callback) { function checkAndProcess() { // 遍历页面所有元素,找出带Shadow根的节点 document.querySelectorAll('*').forEach(el => { if (el.shadowRoot) { const targetButtons = el.shadowRoot.querySelectorAll(selector); targetButtons.forEach(btn => { // 标记已处理,避免重复操作 if (!btn.dataset.handled) { btn.dataset.handled = 'true'; callback(btn); } }); } }); // 每500ms检查一次,适配Ajax加载节奏 setTimeout(checkAndProcess, 500); } checkAndProcess(); } // 在脚本中调用 (function() { waitForShadowElements('button.btn.ripple.primary', function(button) { button.disabled = false; console.log('已启用Shadow DOM内的按钮'); }); })();
方式二:用MutationObserver监听DOM变化
通过监听DOM节点的添加事件,一旦发现带Shadow根的元素,立即处理其内部的目标按钮,同时监听Shadow根内部的变化:
(function() { // 监听整个文档的节点添加 const docObserver = new MutationObserver((mutations) => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { // 检查当前节点是否有Shadow根 if (node.shadowRoot) { processShadowContent(node.shadowRoot); } // 遍历子节点,防止嵌套Shadow DOM node.querySelectorAll('*').forEach(child => { if (child.shadowRoot) { processShadowContent(child.shadowRoot); } }); }); }); }); // 处理Shadow根内的内容 function processShadowContent(shadowRoot) { // 启用已存在的目标按钮 const buttons = shadowRoot.querySelectorAll('button.btn.ripple.primary'); buttons.forEach(btn => btn.disabled = false); // 监听Shadow根内部的节点变化,处理后续加载的按钮 const shadowObserver = new MutationObserver((mutations) => { mutations.forEach(mutation => { mutation.addedNodes.forEach(node => { if (node.matches?.('button.btn.ripple.primary')) { node.disabled = false; } }); }); }); shadowObserver.observe(shadowRoot, { childList: true, subtree: true }); } // 启动文档监听 docObserver.observe(document.body, { childList: true, subtree: true }); })();
2. 选择合适的Run at时机
针对Ajax驱动的网站,优先选择@run-at document-idle,这是Tampermonkey的默认设置,会在文档加载完成、所有资源就绪后执行脚本,确保基础DOM结构已存在,能正常设置监听逻辑。
如果网站的Shadow DOM或Ajax内容加载较早,也可以尝试@run-at document-body,但document-idle更稳妥,能避免因DOM未完全构建导致的监听失效。绝对不要用document-start,此时DOM还未开始渲染,脚本无法获取任何有效节点。
内容的提问来源于stack exchange,提问作者bakedmcgaked

