找回密码
 立即注册
快捷导航

[Html/Css/JS] 油猴脚本 - 找不到元素/iframe/脚本作用域等问题

[复制链接]
庶民 2023-6-13 16:14:43 | 显示全部楼层

找不到元素的问题

一般可能有两个因素,一个是存在ifame,另外一个问题可能是页面加载的问题;

页面加载与元素的那点破事

页面加载不等于元素已经出现,我们通常在f12控制台调试和查看的是现有的页面,并不代表页面加载完毕后就一定具备这个元素

尽管很多页面会在加载的时候就出现所有元素

但是也有很多页面,会在渲染完毕后,再根据xhr请求数据等方式,再在页面上绘制新的数据,这个时候如果我们在页面加载完毕和获取数据绘制之间进行获取元素是获取不到的。

解决方法也很简单,这里我提供两个常见的方法

1.使用Setinterval来进行循环判断,当获取元素不为空的时候继续执行,但需要注意不要创建过多的定时器,以及不使用的时候可以考虑销毁定时器

https://www.runoob.com/jsref/met-win-setinterval.html

// 百度云中等待window中的videojs加载完成的代码
const w = unsafeWndow || window,
      id = setInterval(() => {
        if (myvideojs = myvideojs || w.videoPlayer) {
            if (!!getmyvideo("html5player")) {
                clearInterval(id);
                // 该干嘛干嘛
            }
        }
}, 500);

2.通过DOM插入监控来判断

在bilibili的例子中我们已经搞过了,通过上层已经存在的元素对其进行dom监听,然后再插入的时候会触发这个函数,来进行一些操作

但是需要注意把握好下层元素的数量问题,如果绘制元素过多反复触发,会极大的延迟运行速度

let ops=document.querySelector('#arc_toolbar_report .ops');
//插入三连之后好像会重新生成,不添加就不会重新生成,暂时没弄清什么情况,先这样处理了.
//主要作用是监听ops的DOMNodeInserted事件,等它修改完成之后再插入我们的三连按钮,另外注意run-at是document-end,要等待ops生成之后再监听,不然query返回null会报错
//这个事件会多次调用,但是我们insertBefore插入如果元素存在,只是修改而不会新增
ops.addEventListener("DOMNodeInserted", function(event) {
    let share=document.querySelector('.share');
    share.parentElement.insertBefore(triple,share);
});

iframe的那点破事

可以参考 https://music.163.com/#

我们可以获取到iframe元素后通过conetentWindow进入iframe的作用域来执行相应的函数

document.querySelector('#g_iframe').contentWindow.document.querySelector

在我的印象里好像是contentWindow内的document通常同域下才可以使用,而非同域是没有办法的

但是,如果没法调用contentWindow下的document我们也有其他办法的

通过 //@match 匹配让脚本运行在iframe内就好了!

关于脚本作用域问题

一旦你开启了沙盒模式,你的沙盒中的函数与网页的函数是隔离的

如果你再写代码的过程中网页与沙盒之间产生了一些函数的交互或者调用,一定要注意这个问题

这时候可以考虑在沙盒对网页的元素进行监控或者挂载一些实践

关于这个问题在平时是不常见的,如果发生了这种问题,相信那时候的你是已经知道如何解决得了!

回复

使用道具 举报

主题

0

回帖

1050

积分

功行圆满

 楼主| 庶民 2023-6-13 16:17:57 | 显示全部楼层
本帖最后由 庶民 于 2023-6-13 16:26 编辑

给一个我自己写的函数,用于监听查找的元素是否存在,页面渲染完出现后则操作后续步骤:

function getElement(parent, selector, timeout = 0) {
  return new Promise(resolve => {
    let result = parent.querySelector(selector);
    if (result) return resolve(result);
    let timer;
    const mutationObserver = window.MutationObserver || window.WebkitMutationObserver || window.MozMutationObserver;
    if (mutationObserver) {
      const observer = new mutationObserver(mutations => {
        for (let mutation of mutations) {
          for (let addedNode of mutation.addedNodes) {
            if (addedNode instanceof Element) {
              result = addedNode.matches(selector) ? addedNode : addedNode.querySelector(selector);
              if (result) {
                observer.disconnect();
                timer && clearTimeout(timer);
                return resolve(result);
              }
            }
          }
        }
      });
      observer.observe(parent, {
        childList: true,
        subtree: true
      });
      if (timeout > 0) {
        timer = setTimeout(() => {
          observer.disconnect();
          return resolve(null);
        }, timeout);
      }
    } else {
      const listener = e => {
        if (e.target instanceof Element) {
          result = e.target.matches(selector) ? e.target : e.target.querySelector(selector);
          if (result) {
            parent.removeEventListener('DOMNodeInserted', listener, true);
            timer && clearTimeout(timer);
            return resolve(result);
          }
        }
      };
      parent.addEventListener('DOMNodeInserted', listener, true);
      if (timeout > 0) {
        timer = setTimeout(() => {
          parent.removeEventListener('DOMNodeInserted', listener, true);
          return resolve(null);
        }, timeout);
      }
    }
  });
}

原理是首先用querySelector获取元素,获取不到时用MutationObserver监听插入节点,直到获取到所需元素,页面不支持MutationObserver时改用DOMNodeInserted实现,获取到元素后自动移除相关监听事件。代码看不懂也没关系,直接复制过去用就行了。第1个参数是父节点,第2个参数同querySelector,第3个参数是可选的超时时间(毫秒),设定最长监听时间以防止长时间等待,默认0不设超时。返回类型为Promise,需要用.then链式调用,或者async/await实现类似同步调用的效果,以下是调用示例(推荐后一种方式):

function example1() {
  getElement(document, '#test').then(element => {
    //...
  });
}

async function example2() {
  const element = await getElement(document, '#test');
  //...
}

补充其他提到的:

好像matches有兼容性问题,如果你的浏览器不支持,在代码前加一句:

Element.prototype.matches = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.webkitMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector;



   
回复

使用道具 举报

主题

0

回帖

1050

积分

功行圆满

 楼主| 庶民 2023-6-13 16:22:15 | 显示全部楼层
注意:使用 DOMNodeInserted 在DOM插入监控来判断元素是否存在,要求里面的层级不要过多也不要过于频繁操作元素,否则会严重拖慢运行效率;

可以使用MutationObserver来代替 对元素DOMNodeInserted 的监听,MutationObserver就是用来解决性能问题的,其实DOMNodeInserted老早就被抛弃了,还留着主要是为了兼容性。MutationObserver的原理大概就是他不阻塞页面加载,而是把获取到的节点丢进一个数组里保存,每加载完一部分再触发监听,相比addEventListener每个节点都触发监听要好很多,而且addEventListener返回的是一个event,你可以对他做stopPropagation阻止冒泡之类的操作,在你的函数执行完前页面都要一直等待,而MutationObserver返回的是一个node,他不关心页面渲染的过程,只保存节点的引用...
   
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1楼
2楼
3楼
温馨提示

关于 注册码 问题

      由于近期经常大量注册机器人注册发送大量广告,本站开启免费入群领取注册码注册网站账号,注册码在群公告上贴着...

关于 注册码 问题

      由于近期经常大量注册机器人注册发送大量广告,本站开启免费入群领取注册码注册网站账号,注册码在群公告上贴着...

Archiver|手机版|小黑屋|DLSite

GMT+8, 2024-11-23 06:20

Powered by Discuz! X3.5 and PHP8

快速回复 返回顶部 返回列表