曹贼,莫要动‘我’网站 —— MutationObserver

2,292 阅读9分钟

前言

本篇主要讲述了如何禁止阻止控制台调试以及使用了MutationObserver构造方法禁止修改DOM样式。

正文

话说在三国时期,大都督周瑜为了向世人宣布自己盛世容颜的妻子小乔,并专门创建了一个网站来展示自己的妻子

image.png 这么好看的看的小乔,谁看谁不糊,更何况曹老板。这天,曹操在浏览网页的时候,无意间发现了周瑜的这个网站,看着美若天仙的小乔,曹操的眼泪止不住的从嘴角流了下来。赶紧将网站上的照片保存了下来。
这个消息最后传到了周瑜的耳朵里,他只是想展示小乔,可不是为了让别人下载的。于是在自己的网站上做了一些预防措施。
为了防止他人直接在网站上直接下载图片,周瑜将右键的默认事件给关闭了,并且为了防止有人打开控制台,并对图片保存,采取了以下方法:

禁用右键和F12键

//给整个document添加右击事件,并阻止默认行为
document.addEventListener("contextmenu", function (e) {
    e.preventDefault();
    return false;
  });
  
  //给整个页面禁用f12按键  keyCode即将被禁用 不再推荐使用  但仍可以使用
    document.addEventListener("keydown", function (e) {
    //当点了f3\f6\f10之后,即使禁用了f12键依旧可以打开控制台,所以一并禁用
      if (
        [115, 118, 121, 123].includes(e.keyCode) ||
        ["F3", "F6", "F10", "F12"].includes(e.key) ||
        ["F3", "F6", "F10", "F12"].includes(e.code) ||
        //ctrl+f 效果和f3效果一样 点开搜索之后依旧可以点击f12 打开控制台 所以一并禁用
        //缺点是此网站不再能够  **全局搜索**
        (e.ctrlKey && (e.key == "f" || e.code == "KeyF" || e.keyCode == 70))||
        //禁用专门用于打开控制台的组合键
        (e.ctrlKey && e.shiftKey && (e.key == "i" || e.code == "KeyI" || e.keyCode == 73))
      ) {
        e.preventDefault();
        return false;
      }
    });

当曹操再次想保存小乔照片的时候,发现使用网页的另存了已经没用了。这能难倒曹老板吗,破解方法,在浏览器的右上角进行操作就可打开控制台,这个地方是浏览器自带的,没办法禁用

image.png 这番操作之后,曹操可以选择元素保存那个图片了。周瑜的得知了自己的禁用措施被破解后,赶忙连夜加班打补丁,于是又加了一些操作,禁止打开控制台后进行操作

禁用控制台

如何判定控制台被打开了,可以使用窗口大小来判定

function resize() {
      var threshold = 100;
      //窗口的外部减窗口内超过100就判定窗口被打开了
      var widthThreshold = window.outerWidth - window.innerWidth > threshold;
      var heightThreshold = window.outerHeight - window.innerHeight > threshold;
      if (widthThreshold || heightThreshold) {
        console.log("控制台打开了");
      }
    }
  window.addEventListener("resize", resize);

但是也容易被破解,只要让控制台变成弹窗窗口就可以了

也可以使用定时器进行无限debugger,因为只有在控制台打开的时候debugger才会生效。关闭控制台的时候,并不会影响功能。当前网页内存占用比较大的时候,定时器的占用并不明显。在当前网页占用比较小的时候,一直开着定时器才会有较为明显的提升

  setInterval(() => {
      (function () {})["constructor"]("debugger")();
    }, 500);

破解方法一样有,在debugger的位置右键禁用调试就可以了。这样控制台就可以正常操作了

image.png 既然有方法破解,就还要做一层措施,既然是要保存图片,那就把img转成canvas,这样即使打开控制台也没办法进行对图片的保存

//获取dom
 const img = document.querySelector(".img");
 const canvas = document.querySelector("#canvas");
 //img转成canvas
  canvas.width = img.width;
  canvas.height = img.height;
  ctx = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0, img.width, img.height);
  document.body.removeChild(img);

经过一夜的努力,该加的措施都加上了。周瑜心想这下就没办法保存我的小乔了吧。
来到曹操这边,再次打开周瑜的小破站,还想故技重施时,发现已经有了各种显示,最后也没难倒曹操,那些阻碍也都被破解了。但是到保存图片的时候傻眼了,竟然已经不是图片格式了,那就没办法下载了呀。但是小乔真的很养神,曹操心有不甘,于是使用了最后一招,既然没办法下载那就截图,虽然有损画质,但是依旧能看。

得知如此情况的大都督周瑜不淡定了,从未见过如此厚颜无耻之人,竟然使用截图。

006APoFYly1g2qcclw1frg308w06ox2t.gif 话说魔高一尺,道高一丈,周瑜再次熬夜加班进行对网站的优化。于是使用了全屏水印+MutationObserver监听水印dom的方法。即使截图也让他看着不舒服。

MutationObserver

MutationObserver是一个构造函数,接口提供了监视对 DOM 树所做更改的能力。它被设计为旧的 Mutation Events 功能的替代品,该功能是 DOM3 Events 规范的一部分。
它接收一个回调函数,每当监听的dom发生改变时,就会调用这个函数,函数传入一个参数,数组包对象的格式,里面记录着dom的变化以及dom的信息。

image.png 返回的实例是一个新的、包含监听 DOM 变化回调函数的 MutationObserver 对象。有三个方法observedisconnecttakeRecords

  • observe接收两个参数,第一个为要监听的dom元素,第二个则是一些配置对象,当调用 observe() 时,childListattributes 和 characterData 中,必须有一个参数为 true。否则会抛出 TypeError 异常。配置对象如下:
    • subtree:当为 true 时,将会监听以 target 为根节点的整个子树。包括子树中所有节点的属性,而不仅仅是针对 target。默认值为 false
    • childList:当为 true 时,监听 target 节点中发生的节点的新增与删除(同时,如果 subtree 为 true,会针对整个子树生效)。默认值为 false
    • attributes:当为 true 时观察所有监听的节点属性值的变化。默认值为 true,当声明了 attributeFilter 或 attributeOldValue,默认值则为 false
    • attributeFilter:一个用于声明哪些属性名会被监听的数组。如果不声明该属性,所有属性的变化都将触发通知。
    • attributeOldValue:当为 true 时,记录上一次被监听的节点的属性变化;可查阅监听属性值了解关于观察属性变化和属性值记录的详情。默认值为 false
    • characterDate:当为 true 时,监听声明的 target 节点上所有字符的变化。默认值为 true,如果声明了 characterDataOldValue,默认值则为 false
    • characterDateOldValue:当为 true 时,记录前一个被监听的节点中发生的文本变化。默认值为 false
  • disconnect方法用来停止观察(当被观察dom节点被删除后,会自动停止对该dom的观察),不接受任何参数
  • takeRecords:方法返回已检测到但尚未由观察者的回调函数处理的所有匹配 DOM 更改的列表,使变更队列保持为空。此方法最常见的使用场景是在断开观察者之前立即获取所有未处理的更改记录,以便在停止观察者时可以处理任何未处理的更改。

该构造函数监听的dom即使在控制台中被更改属性或值,也会被监听到。

使用MutationObserver对水印dom进行监听,并限制更改。

<style>
//定义水印的样式
    #watermark {
        width: 100vw;
        height: 100vh;
        position: absolute;
        left: 0;
        top: 0;
        font-size: 34px;
        color: #32323238;
        font-weight: 700;
        display: flex;
        flex-wrap: wrap;
        justify-content: space-evenly;
        align-content: space-evenly;
        z-index: 9999999;
      }
      #watermark span {
        transform: rotate(45deg);
      }
</style>

 <script>
 //获取水印dom
    const watermark = document.querySelector("#watermark");
    //克隆水印dom ,用作后备,永远不要改变
    const _watermark = watermark.cloneNode(true);
    //获取水印dom的父节点
    const d = watermark.parentNode;
    //获取水印dom的后一个节点
    let referenceNode;
    [...d.children].forEach((item, index) => {
      if (item == watermark) referenceNode = d.children[index + 1];
    });
    //定义MutationObserver实例observe方法的配置对象
    const prop = {
      childList: true,//针对整个子树
      attributes: true,//属性变化
      characterData: true,//监听节点上字符变化
      subtree: true,//监听以target为根节点的整个dom树
    };
    //定义MutationObserver
    const observer = new MutationObserver(function (mutations) {
    //在这里每次坚挺的dom发生改变时 都会运行,传入的参数为数组对象格式
      mutations.forEach((item) => {
      //这里可以只针对监听dom的样式来判断
        if (item.attributeName === "style") {
        //获取父节点的所有子节点,因为时伪数组,使用扩展运算符转以下
          [...d.children].forEach((v) => {
          //判断一下,是父节点里的那个节点被改变了,并且删除那个被改变的节点(也就是删除水印节点)
            if (item.target.id && v == document.querySelector(`#${item.target.id}`)) {
              v.remove();
            }
          });
          //原水印节点被删除了,这里使用克隆的水印节点,再次克隆
          const __watermark = _watermark.cloneNode(true);
          //这里的this指向是MutationObserver的实例对象,所以同样可以使用observe监听dom
          //监听第二次克隆的dom
          this.observe(__watermark, prop);
          //因为水印dom被删除了,再将克隆的水印dom添加到原来的位置 就是referenceNode节点的前面
          d.insertBefore(__watermark, referenceNode);
        }
      });
    });
    在初始化的时候监听初始化的水印dom
    observer.observe(watermark, prop);
  </script>

这样,每当对水印dom进行更改样式的时候,就会删除该节点,并重新添加一个初始的水印dom,即使突破重重困难打开开控制台,用户也是无法对dom 进行操作。

视频转Gif_爱给网_aigei_com.gif

隔天曹操再次打开网页,发现网页上的水印,心里不足为惧,心想区区水印能难倒自己?操作到最后却发现,不论如何对水印dom进行操作,都无法改变样式。虽说只是为了保存图片,但是截图有着这样水印,任谁也不舒服呀。曹操大怒,刚吃了两口的饭啪的一下就盖在了桌子上......

20230508094549_33500.gif 然而曹操不知道的是,在控制台中,获取dom节点右键是可以只下载获取的那个节点的......

image.png

结尾

文章主要是以鬼畜恶搞的方式讲述了,如何禁止用户打开控制台(还有重写toSring,consloe.log等一些方法,但我并没有没有实现,所以这里并没有写上),并且如何使用MutationObserver构造函数来监听页面中的dom元素。其实大多情况下并没有这方面的项目需求,完全可以当扩展知识看了。

写的不好的地方可以提出意见,虚心请教!