李恒道 发表于 2021-9-22 11:15:06

[油猴脚本开发指南]MutationObserver实战

# 前言

之前我们实现过b站三联https://bbs.tampermonkey.net.cn/forum.php?mod=viewthread&tid=238

这节课我们来搞一个高清重置版

```js
// ==UserScript==
// @name         重置bilibili三连按钮demo
// @namespace    https://bbs.tampermonkey.net.cn/
// @version      0.1
// @description给bilibili增加一个真三连按钮
// @author       Wyz
// @match      https://www.bilibili.com/video/*
// @grant      none
// @run-at       document-end
// ==/UserScript==

let triple=document.createElement("button");
triple.innerText="三连";
triple.style.background="#757575";//颜色弄得差不多吧
triple.style.color="#fff";
triple.onclick=function(){
    //三连代码
    let httpRequest = new XMLHttpRequest();
    httpRequest.open('POST', 'https://api.bilibili.com/x/web-interface/archive/like/triple');
    httpRequest.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    httpRequest.withCredentials = true;//设置跨域发送
    let aid=window.__INITIAL_STATE__.aid;
    let sKey="bili_jct";
    let csrf=decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
    httpRequest.send('aid='+aid+'&csrf='+csrf);
    httpRequest.onreadystatechange = function () {
      if (httpRequest.readyState == 4 && httpRequest.status == 200) {
            var json = JSON.parse(httpRequest.responseText);
            console.log(json);
            if(json.code==0){
                alert("三连成功!刷新页面可见");
            }else{
                alert("三连失败/(ㄒoㄒ)/~~");
            }
      }
    };
};
let ops=document.querySelector('#arc_toolbar_report .ops');
//插入三连之后好像会重新生成,不添加就不会重新生成,暂时没弄清什么情况,先这样处理了.
//主要作用是监听ops的修改,等它修改完成之后再插入我们的三连按钮,另外注意run-at是document-end,要等待ops生成之后再监听,不然query返回null会报错
//这个事件会多次调用,但是我们insertBefore插入如果元素存在,只是修改而不会新增
ops.addEventListener("DOMNodeInserted", function(event) {
    let share=document.querySelector('.share');
    share.parentElement.insertBefore(triple,share);
});




```

我们需要改的其实很少,改一下addEventListener函数部分就好了,参考

https://bbs.tampermonkey.net.cn/thread-1007-1-1.html

可以知道DOMNodeIbserted等价于childList和subtree为true

所以我们可以大概修改一下代码

```javascript
let triple=document.createElement("button");
triple.innerText="三连";
triple.style.background="#757575";//颜色弄得差不多吧
triple.className='checkinsert'
triple.style.color="#fff";
triple.onclick=function(){
    //三连代码
    let httpRequest = new XMLHttpRequest();
    httpRequest.open('POST', 'https://api.bilibili.com/x/web-interface/archive/like/triple');
    httpRequest.setRequestHeader("Content-type","application/x-www-form-urlencoded");
    httpRequest.withCredentials = true;//设置跨域发送
    let aid=window.__INITIAL_STATE__.aid;
    let sKey="bili_jct";
    let csrf=decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
    httpRequest.send('aid='+aid+'&csrf='+csrf);
    httpRequest.onreadystatechange = function () {
      if (httpRequest.readyState == 4 && httpRequest.status == 200) {
            var json = JSON.parse(httpRequest.responseText);
            console.log(json);
            if(json.code==0){
                alert("三连成功!刷新页面可见");
            }else{
                alert("三连失败/(ㄒoㄒ)/~~");
            }
      }
    };
};
let ops=document.querySelector('#arc_toolbar_report .ops');
let observerOptions = {
    childList: true, // 观察目标子节点的变化,添加或删除
    attributes: true, // 观察属性变动
}

function callback(mutationList, observer) {
    if(document.querySelector('.checkinsert')===null){
      let share=document.querySelector('.share');
      share.parentElement.insertBefore(triple,share);
    }
}

var observer = new MutationObserver(callback);
observer.observe(ops, observerOptions);
```

这里我已经更改成了MutationObserver

注意,这里有一点小心机

我新增了一行triple.className='checkinsert'

并且在callback内首先检查是否不存在document.querySelector('.checkinsert')

如果不存在才开始插入,如果存在就忽略

这样写的原因是如果我们不对其进行检查会导致反复插入

插入又会触发callback回调,从而引发一个死循环的问题。

所以我对按钮加了一个class叫checkinsert,使querySelector可以方便的找到

修改后可以发现页面照常出现了按钮

![图片.png](data/attachment/forum/202109/22/111349h64wh7jye69dyo41.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/300 "图片.png")

经过测试功能没有任何问题

那我们的小实战就结束啦!

# 结语

撒花~

王一之 发表于 2021-9-22 14:39:44

ggnb!这就更新代码

maxzhang 发表于 2021-9-22 17:49:18

ggnb
不像我,只会偷懒触发页面元素click事件、

rubinTime 发表于 2021-12-21 20:58:45

gege,这个是怎么进入到回调中,外面虽然有MutationObserver observe了ops的dom变化,但是添加操作不是在回调里面执行,不是对ops添元素加才了才会触发observe的回调,document.querySelector('.checkinsert')===null用来限制添加一次后触发无限添加这里我能理解(这里用disconnet不加判断是否可以?),但是第一次的回调他是怎么触发的

李恒道 发表于 2021-12-21 21:07:35

rubinTime 发表于 2021-12-21 20:58
gege,这个是怎么进入到回调中,外面虽然有MutationObserver observe了ops的dom变化,但是添加操作不是在回 ...

啥意思...哥哥,看了几遍我没太理解具体疑似

rubinTime 发表于 2021-12-21 21:15:53

李恒道 发表于 2021-12-21 21:07
啥意思...哥哥,看了几遍我没太理解具体疑似

就是这个回调是怎么触发的,不是对目标元素有插入才会触发吗,但是那个插入一件三联的元素插入逻辑不是写在回调函数内而不是回调函数外吗

李恒道 发表于 2021-12-21 22:34:19

rubinTime 发表于 2021-12-21 21:15
就是这个回调是怎么触发的,不是对目标元素有插入才会触发吗,但是那个插入一件三联的元素插入逻辑不是写 ...

let ops=document.querySelector('#arc_toolbar_report .ops');
let observerOptions = {
    childList: true, // 观察目标子节点的变化,添加或删除
    attributes: true, // 观察属性变动
}

function callback(mutationList, observer) {
    if(document.querySelector('.checkinsert')===null){
      let share=document.querySelector('.share');
      share.parentElement.insertBefore(triple,share);
    }
}

var observer = new MutationObserver(callback);
observer.observe(ops, observerOptions);

李恒道 发表于 2021-12-21 22:34:30

rubinTime 发表于 2021-12-21 21:15
就是这个回调是怎么触发的,不是对目标元素有插入才会触发吗,但是那个插入一件三联的元素插入逻辑不是写 ...

    if(document.querySelector('.checkinsert')===null){
      let share=document.querySelector('.share');
      share.parentElement.insertBefore(triple,share);
    }
是在回调函数内呀

李恒道 发表于 2021-12-21 22:35:03

rubinTime 发表于 2021-12-21 21:15
就是这个回调是怎么触发的,不是对目标元素有插入才会触发吗,但是那个插入一件三联的元素插入逻辑不是写 ...

大概这个逻辑
开始监听页面
插入元素触发回调
回调检测是否存在对应元素
不存在则插入,如果存在则不管本次监听

rubinTime 发表于 2021-12-21 22:42:17

意思是说最开始的时候是页面加载的时候渲染出ops的子元素触发了第一次的回调,之后是添加一键三连按钮触发回调
页: [1] 2
查看完整版本: [油猴脚本开发指南]MutationObserver实战