项目背景:react + antd 框架下,开发的,项目已经基于雏形。
项目要求:项目产品经理要求在已有的项目页面上添加水印,防止用户截图可以追踪起源,所以就打算添加水印的需求。
刚接到这个需求时,在网上找到了一些水印的生成,再结合当前项目进行调整,想着,在页面上层添加一个浮层,全部由水印打的,然后在通过pointerEvents = 'none';
让水印不遮挡页面的点击事件。
水印的生成代码如下:
// 水印
// eslint-disable-next-line no-shadow-restricted-names
export const watermark = () => {
const currentElement = document.getElementById('body');
// 默认设置
const defaultSettings = {
watermark_txt: 'text', // 填充的内容
watermark_x: 100, // 水印起始位置x轴坐标
watermark_y: 20, // 水印起始位置Y轴坐标
watermark_rows: 20, // 水印行数
watermark_cols: 20, // 水印列数
watermark_x_space: 100, // 水印x轴间隔
watermark_y_space: 50, // 水印y轴间隔
watermark_color: '#aaa', // 水印字体颜色
watermark_alpha: 0.4, // 水印透明度
watermark_fontsize: '15px', // 水印字体大小
watermark_font: '微软雅黑', // 水印字体
watermark_width: 210, // 水印宽度
watermark_height: 80, // 水印长度
watermark_angle: 20, // 水印倾斜度数
};
if (document.getElementById('otemp_water')) {
document.getElementById('otemp_water').remove();
}
const oTemp = document.createElement('div');
oTemp.id = 'otemp_water';
// todo
oTemp.style.pointerEvents = 'none';
oTemp.style.position = 'absolute';
oTemp.style.top = '0';
oTemp.style.zIndex = '999';
oTemp.style.zIndex = '999';
oTemp.style.flex = 'auto';
oTemp.style.width = 'calc(100% - 210px)';
oTemp.style.overflow = 'hidden';
oTemp.style.height = '1000%';
// 获取页面最大宽度
let page_width = Math.max(document.body.scrollWidth, document.body.clientWidth);
const cutWidth = page_width * 0.015;
page_width -= cutWidth;
// 获取页面最大高度
let page_height = Math.max(document.body.scrollHeight, document.body.clientHeight);
page_height = Math.max(page_height, window.innerHeight - 30);
oTemp.style.height = `${page_height}px`;
// 如果将水印列数设置为0,或水印列数设置过大,超过页面最大宽度,则重新计算水印列数和水印x轴间隔
if (
defaultSettings.watermark_cols === 0 ||
parseInt(
defaultSettings.watermark_x +
defaultSettings.watermark_width * defaultSettings.watermark_cols +
defaultSettings.watermark_x_space * (defaultSettings.watermark_cols - 1),
10
) > page_width
) {
defaultSettings.watermark_cols = parseInt(
(page_width - defaultSettings.watermark_x + defaultSettings.watermark_x_space) /
(defaultSettings.watermark_width + defaultSettings.watermark_x_space),
10
);
defaultSettings.watermark_x_space = parseInt(
(page_width -
defaultSettings.watermark_x -
defaultSettings.watermark_width * defaultSettings.watermark_cols) /
(defaultSettings.watermark_cols - 1),
10
);
}
// 如果将水印行数设置为0,或水印行数设置过大,超过页面最大长度,则重新计算水印行数和水印y轴间隔
if (
defaultSettings.watermark_rows === 0 ||
parseInt(
defaultSettings.watermark_y +
defaultSettings.watermark_height * defaultSettings.watermark_rows +
defaultSettings.watermark_y_space * (defaultSettings.watermark_rows - 1),
10
) > page_height
) {
defaultSettings.watermark_rows = parseInt(
(defaultSettings.watermark_y_space + page_height - defaultSettings.watermark_y) /
(defaultSettings.watermark_height + defaultSettings.watermark_y_space),
10
);
defaultSettings.watermark_y_space = parseInt(
(page_height -
defaultSettings.watermark_y -
defaultSettings.watermark_height * defaultSettings.watermark_rows) /
(defaultSettings.watermark_rows - 1),
10
);
}
let x = 0;
let y = 0;
for (let i = 0; i < defaultSettings.watermark_rows; i += 1) {
y =
defaultSettings.watermark_y +
(defaultSettings.watermark_y_space + defaultSettings.watermark_height) * i;
for (let j = 0; j < defaultSettings.watermark_cols; j += 1) {
x =
defaultSettings.watermark_x +
(defaultSettings.watermark_width + defaultSettings.watermark_x_space) * j;
const mask_div = document.createElement('div');
mask_div.id = `mask_div${i}${j}`;
mask_div.className = 'mask_div';
mask_div.appendChild(document.createTextNode(defaultSettings.watermark_txt));
// 设置水印div倾斜显示
mask_div.style.webkitTransform = `rotate(-${defaultSettings.watermark_angle}deg)`;
mask_div.style.MozTransform = `rotate(-${defaultSettings.watermark_angle}deg)`;
mask_div.style.msTransform = `rotate(-${defaultSettings.watermark_angle}deg)`;
mask_div.style.OTransform = `rotate(-${defaultSettings.watermark_angle}deg)`;
mask_div.style.transform = `rotate(-${defaultSettings.watermark_angle}deg)`;
mask_div.style.visibility = '';
mask_div.style.position = 'absolute';
mask_div.style.left = `${x}px`;
mask_div.style.top = `${y}px`;
mask_div.style.overflow = 'hidden';
mask_div.style.zIndex = '9999';
// 让水印不遮挡页面的点击事件
mask_div.style.pointerEvents = 'none';
mask_div.style.opacity = defaultSettings.watermark_alpha;
mask_div.style.fontSize = defaultSettings.watermark_fontsize;
mask_div.style.fontFamily = defaultSettings.watermark_font;
mask_div.style.color = defaultSettings.watermark_color;
mask_div.style.textAlign = 'center';
mask_div.style.width = `${defaultSettings.watermark_width}px`;
mask_div.style.height = `${defaultSettings.watermark_height}px`;
mask_div.style.display = 'block';
oTemp.appendChild(mask_div);
}
}
currentElement.appendChild(oTemp);
};
水印生成好之后,就是讲水印放在对应的页面上,通过const currentElement = document.getElementById('body');
这个currentElement
就是我们需要把 对应的水印放在哪一个DOM树下。
弯道一 使用 componentWillReceiveProps 生命周期,通过路由的变化进行页面水印填充
刚开始是通过在路由填充页面,通过componentWillReceiveProps
生命周期,在路由发生变化时,再将水印重新填充和刷新。
import { watermark } from '@/utils/utils';
...
componentWillReceiveProps() {
watermark();
}
但是发现一个问题,因为componentWillReceiveProps
是在路由发生变化时,会进行调用,但是页面填充 render
时,页面会进行数据的刷新,导致页面高度发生变化,这样就会导致,在调用watermark()
函数时,不能正确获取页面的可视高度,导致水印填充不完美,要么超过页面高度,要么获取的高度太低,填充不满。
弯道二 通过监听页面宽高的变化,进行填充水印
import { watermark } from '@/utils/utils';
...
componentDidMount() {
if (window.attachEvent) {
window.attachEvent('onresize', () => watermark());
} else if (window.addEventListener) {
window.addEventListener('resize', () => watermark(), false);
}
}
componentWillUnmount() {
if (window.detachEvent) {
window.detachEvent('onresize', () => watermark());
} else if (window.removeEventListener) {
window.removeEventListener('resize', () => watermark(), false);
}
}
onresize
事件会在窗口或者框架被调整大小时发生变化。 虽然说框架大小也会发生变化,但是实际发现只有在窗口大小发生变化时,才会执行这个方法,并且页面刷新的时候是不会执行当前方法的,所以这个方法又不行!!!
正道
参考:zhuanlan.zhihu.com/p/41418813
import ResizeObserver from 'resize-observer-polyfill';
import { watermark } from '@/utils/utils';
...
constructor(props) {
super(props);
this.resizeObserver = undefined;
}
componentDidMount() {
this.resizeObserver = new ResizeObserver(() => {
watermark();
});
// 当document.getElementById('body') DOM 宽高发生变化时,
this.resizeObserver.observe(document.getElementById('body'));
}
componentWillUnmount() {
if (this.resizeObserver) {
this.resizeObserver.unobserve(document.getElementById('cms-demo-right'));
}
}
在网上查了下关于ResizeObserver API
, 当我们需要知道一个元素的大小发生变化或者屏幕横屏时,我们需要监听 window.resize
事件或者 window.orientationchange
方法。 由于 reize
事件会在一秒内触发将近60次,所以很容易在改变窗口大小时导致性能问题。换句话说,window.resize
事件通常是浪费的,因为它会监听每个元素的大小变化(只有 window 对象才有 resize事件),而不是具体到某个元素的变化。如果我们只想监听某个元素的变化的话,这种操作就很浪费性能了。
而刚好,ResizeObserver API
就可以帮助我们: 监听一个 DOM 节点的变化,这种变化包括但不仅限于:
- 某个节点的出现和隐藏
- 某个节点的大小的变化
实际上, ResizeObserver API
使用了观察者模式,也就是我们常说的发布- 订阅模式。 发布-订阅模式是 JavaScript
中典型的设计模式,
- observe
语法:
const myObserver = new ResizeObserver(entries => {
entries.forEach(entry => {
console.log(`大小位置:${entry.contentRect}`);
console.log(`监听的DOM:${entry.target}`);
})
})
myObserver.observe(document.body)
以上,调用实例对象的observe
方法,监听整个body
节点的变化,当改变窗口大小或者某个DOM
节点出现或隐藏时,就会触发回调。
- unobserve
unobserve
方法,顾名思义了,就是取消监听某个DOM节点。比如说想在两秒后取消监听document.body,那么这样做就好了
componentWillUnmount() {
if (this.resizeObserver) {
this.resizeObserver.unobserve(document.getElementById('cms-demo-right'));
}
}