最近公司需要在管理后台加一个水印方案~ 项目用的umi方案,以为就是改一个配置的问题,后来发现坑点还蛮多~ 希望此稳定能帮助到用umi 的你们.
一. 先来说说心路历程
坑点1
umi的水印适配只能在layout
中进行配置,也就是路由配置中layout为false的页面无法配置水印,比如说登录页~
坑点2
就算只对layout配置也会有问题,如下是配置与效果
可以看到头部区域,菜单区域以及空白区域适配不到.
坑点3
既然配置不好处理,而umi用的是react的ant组件,那是不是可以用ant的水印组件Watermark
去包裹根组件达到效果呢?
想法是好的. 实际情况是umi的app.tsx
只是一个配置文件,它并没有暴露根组件,也就是说我们只能用原生js拿到根节点~ 但就算拿到根节点,你又如何去适配Watermark组件呢? 原生与jsx如何进行写法上的兼容?
坑点4
因为以上诸多不便,我不得不尝试寻找其它可替代方案. 比如去github上寻找原生相关的水印方案库~ 反复查找下锁定了一个start比较多的开源水印库:watermark-dom
这个库的优点是简单易用,对水印有保护策略的加持; 缺点是最新版本更新与2019年,对ts的兼容性不是很好,水印内容只能文本不能是图片,且适配不是很好,不能随着内容变化而变化,还会出现滚动条等问题~
所以这个库我们只能作为备选方案…
心路总结
基于以上心路,最后决定还是回到坑点3,尝试手动适配
二. 核心解决思路
- 通过react18的
createRoot
,render出水印组件对应的dom - 通过
MutationObserver
监听dom的变化,并在异步回调中做处理 - 拿到水印dom之后将其append到
masteroot
根节点之中 - 存储水印文案,判断是否变化.
- 变化了的话重复1步骤render创建水印,删除旧的水印,append新的水印dom
- 最后通过
observer.disconnect()
注销监听
三. 实现代码
如下是我的最终调试代码,在app.tsx文件中调用即可
import { Watermark } from 'antd';
import { createRoot } from 'react-dom/client';
let oldval: string = '';
const creatWatermark = (content: string) => {
console.log(oldval, content);
if (oldval && oldval === content) return;
const opt = {
content: content,
fontColor: 'red',
fontStyle: 'normal',
fontSize: 23,
gapX: [30, 30],
rotate: 10,
};
oldval = content;
// 选择需要观察变动的节点
const targetNode = document.createElement('div');
// 解析AST&挂载(是异步,所以后续需要监听节点变化再做处理)
createRoot(targetNode).render(<Watermark {...opt} />);
// 观察器的配置(需要观察什么变动, childList只能向下观察到一级, subtree可多级 )
const config = { childList: true, subtree: true };
// 当观察到变动时执行的回调函数
const callback = (
mutationsList: MutationRecord[],
observer: MutationObserver,
) => {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
const rootMaster = document.getElementById(
'root-master',
) as HTMLElement;
const newDom = targetNode?.childNodes[0]?.childNodes[0] as HTMLElement;
if (newDom) {
document.getElementById('watermark')?.remove();
newDom.id = 'watermark';
rootMaster.appendChild(newDom);
observer.disconnect(); // 注销
}
}
}
};
// 创建一个观察器实例并传入回调函数 MutationObserver的回调是微任务
const observer = new MutationObserver(callback);
// 以上述配置开始观察目标节点
observer.observe(targetNode, config);
// 方案二: 用动画帧刷新也可以(弃用)
// const getFormRefLoop = () => {
// window.requestAnimationFrame(() => {
// const rootMaster = document.getElementById('root-master') as HTMLElement;
// const w = root?.childNodes[0]?.childNodes[0] as HTMLElement;
// if (rootMaster && w) {
// const old = document.getElementById('root');
// if (old) old.remove();
// w.id = 'root';
// rootMaster.appendChild(w);
// } else {
// getFormRefLoop();
// }
// });
// };
// getFormRefLoop();
};
export default creatWatermark;
小结
demo地址
原创不易,转载请注明来源. 如果对你有帮助,收藏关注加点赞哦😝 当然如果大家有更好的解决方案,欢迎评论哦