背景
在古茗日常业务中,经常会给加盟商下发各种资料,例如:奶茶的配方、设备的清洗、卫生的标准等等等。这些资料都是一些内部资料,从信息安全维度不能被泄露和盗取出去。所以会给下发的资料加上水印。这些资料可能是纯文本,也可能是文本加图片的。因此,我们要做好以下两个方面:
- 通过对页面增加水印,可以从系统级别防止别人盗取我们的页面信息
- 通过对单独的图片加水印 - 防止图片保存时没有水印
页面水印
方案设计
实现页面水印的方式有很多,可以看一些常用页面加水印的方案,具体如下:
- 方案一:fixed 定位的 div 元素,重复渲染 div 元素来添加水印。会创建很多无关的 DOM 元素
- 方案二:fixed 定位 canvas 元素,重复填充水印。始终会创建一个无关的 canvas 元素
- 方案三:canvas + 伪类。不会创建无关元素,且兼容性好
- 方案四:svg + 伪类。不会创建无关元素,但兼容性略差于 canvas
这些方案,都有一个通用的缺点,那就是将元素删掉,或者将类名删掉,都能去除页面水印。
基于实现成本和安全性维度的考虑,最终方案选型:方案三,同时增加了通过MutationObserver - Web API 接口参考 | MDN 解决了删除类名导致水印删除的问题。
核心功能点:
- 把签名信息,通过 Canvas 生成背景图
- 利用伪类将背景图添加到需要生成水印的区域上
- 通过 MutationObserver , 解决了删除类名导致水印删除的问题
代码实现
把签名信息,通过Canvas生成背景图
- 利用 Canvas 来绘制背景图,背景内容为水印的内容
- 通过 toDataURL 将 Canvas 转换成图片,格式为 image/png
1 | js复制代码interface IImgOptions { |
利用伪类将背景图添加到整个页面上
- 给需要添加水印元素添加一个对应的伪元素,将第一步通过 Canvas 生成的 data url 作为背景
- 创建一个 style 元素,将伪元素放在 style.innerHTML 中,然后 appendChild 到 head 中,此时,页面水印就加完了
1 | js复制代码const genWaterMark = ({ |
页面效果如下:脱敏处理,截图未展示姓名和手机号。
利用MutationObserver,防止被人删除className
1 | js复制代码const listenerDOMChange = (className: string) => { |
注意点
注意点①:
- 问题:
上述方案的水印是占据整个页面的,但有些水印期望是在特定区域的。
- 解决方案:
利用定位,实现在特定区域增加水印
1 | js复制代码// 通过设置position: absolute来实现 |
页面效果如下:
图片水印
方案设计
在资料中,存在很多图片,但页面水印,对图片你来说就我们要对图片进行预览并且支持保存。此时页面背景水印就没有用啦,我们下载下来的图片还是不带水印的。针对这种现象,我们有以下一些常用的解决方案
- 方案一:服务端添加水印,安全,但是服务端压力大且性能慢
- 方案二:借助 oss 添加水印,简便但是不通用
- 方案三:canvas 方案,安全但性能慢
本文着重介绍后两种前端添加水印的方式。
代码实现
借助oss
将oss地址转成带水印的oss地址
1 | js复制代码// oss水印中的文字进行url安全的base64编码 |
页面效果如下:
)
注意点
注意点①
- 问题:有些图片某些区域是透明的,导致透明的区域上不了色。(效果如图一)
- 解决方案:ui 告诉我们,png 图片导出默认是透明的,但是 jpg 默认会将透明的地方填充白色的背景,所以,我们查阅对图片进行格式转换的参数说明及实例_对象存储-阿里云帮助中心文档得出,只需要加上 x-oss-process=image/format,jpg, 对之前的 genOSSImageWaterMark 进行改造,对非 jpg 的图片都转成 jpg 的图片
1 | js复制代码const genOSSImageWaterMark = (imgSrc: string) => { |
效果如下:
注意点②
- 问题:字体写死,导致水印在大图上特别小,小图上特别大。(效果如图二)
- 解决方案:根据图片比,计算字体大小。
1 | js复制代码interface IImageProps { |
效果如下:
)
注意点③
- 问题:
用户直接将后缀删了,水印也就没了
- 解决方式:
oss 设置安全级别,不带水印不可访问
通过canvas给图片增加水印
技术方案设计
- 图片路径转成 canvas
- canvas 添加水印
- canvas 转成 img
代码实现
1 | js复制代码const genOSSImageWaterMark = async (imgSrc: string) => { |
使用
1 | js复制代码const genOSSImageWaterMark = async (imgSrc: string) => { |
图片路径转成canvas
1 | js复制代码const imgSrc2Canvas = (cav: HTMLCanvasElement, imgSrc: string) => { |
canvas添加文字水印
- 通过二维数组的渲染,来填充文本
- 通过画布的宽度以及水印的宽度来计算 X 轴的渲染次数
- 通过画布的宽度以及你想打印的疏密程度来计算 Y 轴的渲染次数
- 通过画布的宽度以及水印的宽度来计算 X 轴的渲染次数
1 | js复制代码const addWatermark = async (canvas, imgSrc) => { |
页面效果如下:
注意点
注意点①:
- 问题:页面报错如下
- 原因:当 img 元素的 src 不符合同源准则时,会阻止读取 canvas 的内容。因为此时 img 元素放在 canvas 中时,canvas 元素会被标记为被污染的,而在被污染的 canvas 中调用 toDataUrl 将会报错
- 解决方案:
1 | js复制代码// 为image设置crossOrigin属性 |
注意点②:
- 问题:渲染的图片为透明的图片
- 原因:图片还未渲染完,就返回了 canvas。
- 解决方案:等图片渲染完了,再开始画到 canvas 中
1 | js复制代码await new Promise((resolve) => (image.onload = resolve)); |
总结
本文主要讲了两个话题:页面水印 & 图片水印。页面水印很简单,基本上就是利用 canvas 渲染水印,再利用伪类将 canvas 的水印渲染在特定的区域。图片相对而言会复杂一些,在渲染水印之前,得先把图片渲染上去,针对大图,性能可能会慢一点。所以,如果对水印要求不是很严格并且图片是存储在 oss 的,那利用 oss 来加水印也不失为一种好选择。但如果从安全性来考虑,那肯定是服务端加水印会更合适一点。
最后
📚 小茗文章推荐:
关注公众号「Goodme前端团队」,获取更多干货实践,欢迎交流分享~
本文转载自: 掘金