>>前端面试必备的大厂题库<<
当老板要求你为了防止信息泄露,要在公司内部系统中增加水印的时候,你只要掏出以下解决方案,就能快速支持上线,效率杠杠的!
众所周知,水印有明水印和暗水印之分,今天我们讲两种水印,并实现全屏覆盖。
创建一个水印图层,我们需要两步。一步是生成对应的图片,第二步是把图片放到最上层并全屏显示,最好还是按照格子状页面上显示多个水印内容。
因为不同页面大小不同,不同身份的人也应该设置不同的水印信息。这就需要我们动态生成图片,这里就需要使用canvas。
function createBackgroundImage(content, proportion, tiltAngle) {
const can = document.createElement('canvas')
can.width = document.body.clientWidth / proportion
can.height = document.body.clientHeight / proportion
const context = can.getContext('2d')
context.rotate(-25 * Math.PI / 180);
context.font = "800 30px Microsoft JhengHei";
context.fillStyle = "#000";
context.textAlign = 'center';
context.textBaseline = 'Middle';
context.fillText(content, 100, 100)
return can.toDataURL("image/png")
}
可以按照上面的方法创建canvas并填充文字。然后该函数返回一个对应的图片base64给我们。
这里需要注意一下该方法的入参(文字,填充比例,倾斜角度),虽然是按照比例设置的cancas宽高,却没有在fillText上做处理,实际应用的时候,如果适配不同尺寸屏幕还是需要自己再写一下的哈,同时还有文字的大小。
布局相对简单一些,我们需要用到 backgroundImage
属性。
background-image 属性为元素设置背景图像。
元素的背景占据了元素的全部尺寸,包括内边距和边框,但不包括外边距。
默认地,背景图像位于元素的左上角,并在水平和垂直方向上重复。
之后,我们只需要再页面上添加一个div标签,并设置对应的样式,让它占据全屏就行。
下面再给一个简单示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
\#content {
width: 100%;
height: 100vh;
margin-left: auto;
margin-right: auto;
background: cadetblue;
overflow: hidden;
}
</style>
</head>
<body>
<div id="content">
</div>
<script>
function createBackgroundImage(content, proportion, tiltAngle) {
const can = document.createElement('canvas')
can.width = document.body.clientWidth / proportion
can.height = document.body.clientHeight / proportion
console.log('can.width', can.width)
console.log('can.height', can.height)
const context = can.getContext('2d')
context.rotate(-25 * Math.PI / 180);
context.font = "800 30px Microsoft JhengHei";
context.fillStyle = "#000";
context.textAlign = 'center';
context.textBaseline = 'Middle';
context.fillText(content, 100, 100)
return can.toDataURL("image/png")
}
const div = document.getElementById('content')
console.log('div', div)
div.style.backgroundImage = `url(${createBackgroundImage('伯约同学', 6, 10)})`
</script>
</body>
</html>
但是明水印比较好清除,而且对于一些没做处理的图片,当用户直接保存的时候,是没有水印的,这时候信息泄露问题依然存在。为了解决这样的问题,我们需要用到暗水印。
我们知道图片是由多个像素点组成的,通过canvas的getImageData
方法,我们可以得到画布指定矩形的像素数据。
getImageData() 方法返回 ImageData 对象,该对象拷贝了画布指定矩形的像素数据。
对于 ImageData 对象中的每个像素,都存在着四方面的信息,即 RGBA 值:
值得注意的是:RGB 分量值的小量变动,是肉眼无法分辨的,不影响对图片的识别。这是我们在图片上添加暗水印的基石
color/alpha 以数组形式存在,并存储于 ImageData 对象的data属性中。
以下代码可获得被返回的 ImageData 对象中第一个像素的 color/alpha 信息:
red=imgData.data[0];
green=imgData.data[1];
blue=imgData.data[2];
alpha=imgData.data[3];
感兴趣的同学可以打印看一下效果:
function createBackgroundImage(content, proportion, tiltAngle) {
const can = document.createElement('canvas')
can.width = document.body.clientWidth / proportion
can.height = document.body.clientHeight / proportion
const context = can.getContext('2d')
context.rotate(-25 * Math.PI / 180);
context.font = "800 30px Microsoft JhengHei";
context.fillStyle = "#000";
context.textAlign = 'center';
context.textBaseline = 'Middle';
context.fillText(content, 100, 100)
console.log(context.getImageData(0, 0, can.width, can.height))
return can.toDataURL("image/png")
}
const div = document.getElementById('content')
console.log('div', div)
div.style.backgroundImage = `url(${createBackgroundImage('伯约同学', 6, 10)})`
如下所示,给出了对应图片的ImageData结果,它有以下几个属性:
data: Uint8ClampedArray(52752) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …]
colorSpace: "srgb"
height: 42
width: 314
Uint8ClampedArray则是对应的像素点,每四个表征一个像素点,然后从左往右,从上往下的顺序进行排列。
把加密图案放到原图中,如果原图中对应的重叠像素点有内容,则最低位为1,否则为0。
function mergeData(rawImageSrc, watermarkImageSrc) {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = function () {
const myCanvas = document.createElement("canvas");
myCanvas.width = img.width;
myCanvas.height = img.height;
const ctx = myCanvas.getContext("2d")
const bit = 0
const offset = 3
const oImageData = getImageData(rawImageSrc)
const oData = oImageData.data
const newData = getImageData(watermarkImageSrc).data
for (let i = 0; i < oData.length; i++) {
if (i % 4 === bit) {
// 只修改目标通道
if (newData[i + offset] === 0 && (oData[i] % 2 === 1)) {
// 没有信息的像素,将目标通道的奇数像素改为偶数
if (oData[i] === 255) {
oData[i]--
} else {
oData[i]++
}
} elseif (newData[i + offset] !== 0 && (oData[i] % 2 === 0)) {
// 有信息的像素
oData[i]++
}
}
}
ctx.putImageData(oImageData, 0, 0)
resolve(myCanvas.toDataURL("image/png"))
}
img.src = rawImageSrc
})
function getImageData(image) {
const img = new Image()
img.src = image
const myCanvas = document.createElement("canvas");
myCanvas.width = img.width;
myCanvas.height = img.height;
const myContext = myCanvas.getContext("2d")
myContext.drawImage(img, 0, 0);
return myContext.getImageData(0, 0, myCanvas.width, myCanvas.height)
}
对应的解密方法:
function decrypt(watermarkImage) {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = function () {
const myCanvas = document.createElement("canvas");
myCanvas.width = img.width;
myCanvas.height = img.height;
const ctx = myCanvas.getContext("2d")
const imageData = getImageData(watermarkImage)
var data = imageData.data;
for (var i = 0; i < data.length; i++) {
if (i % 4 == 0) {
// 红色分量
if (data[i] % 2 == 0) {
data[i] = 0;
} else {
data[i] = 255;
}
} elseif (i % 4 == 3) {
// alpha通道不做处理
continue;
} else {
// 关闭其他分量,不关闭也不影响答案,甚至更美观 o(^▽^)o
data[i] = 0;
}
}
ctx.putImageData(imageData, 0, 0)
resolve(myCanvas.toDataURL("image/png"))
}
img.src = watermarkImage
})
}
核心示例代码:
const image1 = createBackgroundImage('伯约', 3, 10)
const image2 = createBackgroundImage('学', 3, 10)
mergeData(image1, image2).then(res => {
console.log('res', res)
decrypt(image2).then(res => {
console.log('finalImage', res)
})
})
展示上面的res和finallImage并进行对比大仙,一张是两个字:伯约。另一张只有一个字:学。
诚然,上面这个方法并不普适,毕竟加密解密方法都写成固定的了,不过思路是统一的,那就是都在原图的基础上修改像素点。
本文转自:https://segmentfault.com/a/1190000041398111,如有侵权,请联系删除。
还没有使用过我们的刷题网站(https://fe.ecool.fun/)或者小程序前端面试题宝典的同学,如果近期准备或者正在找工作,千万不要错过,题库主打题全和更新快哦~。
有会员购买、辅导咨询的小伙伴,可以通过下面的二维码,联系我们的小助手。