前言
阅读本文前需要有一定原生JS基础
建议阅读
基本原理
[ShadowRoot基本原理]
https://developer.mozilla.org/zh-CN/docs/Web/API/Web_components/Using_shadow_DOM
[油猴脚本引用库基本原理]
https://learn.scriptcat.org/docs/category/%E5%BC%95%E7%94%A8%E5%BA%93%E4%BD%BF%E7%94%A8/
基本用法
[油猴脚本开发指南]SweetAlert2的漂亮对话框
https://bbs.tampermonkey.net.cn/thread-1203-1-1.html
[油猴脚本开发指南]SweetAlert2进阶
https://bbs.tampermonkey.net.cn/thread-1303-1-1.html
为什么要魔改
油猴脚本属于逆向开发,在原有网站基础上使用脚本管理器及脚本代码对网站原有内容进行修改。
一般JS库都是为网站正向开发准备的,脚本逆向开发过程中许多需要考虑的要点可能是脚本专属要点,正向开发不会涉及,自然正向开发的JS库也不会考虑。
因此脚本逆向开发不可避免的会和网站产生冲突,例如JS冲突、CSS冲突,为了消除这种冲突我们就需要对库代码进行修改优化,本文称之为魔改。
JS冲突由于有脚本沙盒环境,因此我们不必过多考虑。
而CSS冲突就需要我们自己解决。常见的CSS冲突有脚本CSS主动污染网页CSS以及脚本CSS被动被网页全局CSS污染。为了解决这一冲突,我们可以使用ShadowRoot去隔离脚本CSS以及网页CSS。
而sweetalert2并未考虑ShadowRoot,所以我们需要帮助他去考虑。
魔改过程
略
(建议分析sweetalert2源码 不详细展开 有空再说)
sweetalert2中提供了一个选项target="body" //The container element for adding popup into.
但这并不能支持ShadowRoot,因为在源码中很粗暴的静态定义了多个获取函数均以document.body
为基点没有回旋余地。
但好消息是因为有这个自定义挂载容器选项,意味着我们需要修改的代码不会很多,至少兼容了一半,我们只需要魔改另一半即可。
既然document.body是静态定义的,那么我们只需要将其修改成动态函数即可
例如
// 定义挂载容器
let target = document.body;
// 修改挂载容器函数
const SetTarget = (newTarget) => (target = newTarget);
// 挂载容器函数
const GetTarget = () => target;
创建一个局部变量,默认值为document.body
,并为这个局部变量创建2个函数Getter/Setter,并将原始JS中静态定义的document.body
替换成GetTarget()
,当需要修改挂载容器时调用SetTarget()
即可。
又由于sweetalert2的默认设置target="body"
,因此为了减少代码量,我们可以使用.mixin()
方法,来永久修改默认target
,这样就不必要每次都单独设置target
了
Swal = Swal.mixin({ target: container })
** container为ShadowRoot内挂载节点变量,由于Swal2代码原因必须为ShadowRoot创建一个子元素并将target挂载在子元素上,不能直接为ShadowRoot,否则会产生冲突
魔改代码
// ==UserScript==
// @name 魔改sweetalert2支持shadowRoot
// @namespace https://scriptcat.org/
// @description 有一说一Swal2的动画还是好看的,其他的就算了
// @version 0.1.0
// @author DreamNya
// @match https://bbs.tampermonkey.net.cn/
// @resource SwalJS https://cdn.jsdelivr.net/npm/sweetalert2@11.7.20/dist/sweetalert2.js
// @resource SwalCSS https://cdn.jsdelivr.net/npm/sweetalert2@11.7.20/dist/sweetalert2.min.css
// @grant unsafeWindow
// @grant GM_getResourceText
// @run-at document-start
// ==/UserScript==
// 定义挂载容器
let target = document.body;
// 修改挂载容器函数
const SetTarget = (newTarget) => (target = newTarget);
// 挂载容器函数
const GetTarget = () => target;
// 读取 @resource SwalJS 并魔改
const SwalJS = GM_getResourceText("SwalJS")
.replace(
'"undefined"!=typeof window&&/^ru\\b/.test(navigator.language)&&location.host.match(/\\.(ru|su|by|xn--p1ai)$/)',
"false"
) //去除私货
.replace(/document\.body/g, "GetTarget()"); //重定义容器
// 通过eval引入魔改后的sweetalert js
eval(SwalJS);
// 读取 @resource SwalCSS 并魔改
const SwalCSS = GM_getResourceText("SwalCSS").replace(/body/g, "");
// 创建shadowRoot节点
const div = document.createElement("div");
const shadowRoot = div.attachShadow({ mode: "open" });
(document.body || document.firstElementChild).append(div);
// shadowRoot添加样式
const style = document.createElement("style");
style.innerText = SwalCSS;
shadowRoot.append(style);
// shadowRoot添加容器
const container = document.createElement("div");
container.classList.add(".container");
shadowRoot.append(container);
// 重定义Swal节点
SetTarget(container);
// 配置默认弹出节点为container
Swal = Swal.mixin({ target: container });
// 暴露变量至网页环境
unsafeWindow.Swal = Swal;
unsafeWindow.onload = () => Swal.fire("完结撒花", "魔改大成功", "success");
完结撒花
有一说一Swal2的动画还是好看的,其他的就算了,可惜没找到代替库,将就用了。