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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 06:30:09