时间:2023-07-21 09:42:01 | 来源:网站运营
时间:2023-07-21 09:42:01 来源:网站运营
从破解某定设计网站谈前端水印(详细教程):https://github.com/hua1995116/node-demo/tree/master/watermark
水印(watermark)是一种容易识别、被夹于纸内,能够透过光线穿过从而显现出各种不同阴影的技术。水印的类型有很多,有一些是整图覆盖在图层上的水印,还有一些是在角落。
pointer-events
。当它的被设置为pointer-events
CSS 属性指定在什么情况下 (如果有) 某个特定的图形元素可以成为鼠标事件的 target。
none
的时候,能让元素实体虚化,虽然存在这个元素,但是该元素不会触发鼠标事件。详情可以查看 CSS3 pointer-events:none应用举例及扩展 « 张鑫旭-鑫空间-鑫生活 。// 文本内容<div class="app"> <h1>秋风</h1> <p>hello</p></div>
首先我们来生成一个水印块,就是上面的 一个个秋风的笔记
。这里主要有一点就是设置一个透明度(为了让水印看起来不是那么明显,从而不遮挡我们的主要页面),另一个就是一个旋转,如果是正的水平会显得不是那么好看,最后一点就是使用 userSelect
属性,让此时的文字无法被选中。CSS 属性user-select
控制用户能否选中文本。除了文本框内,它对被载入为 chrome 的内容没有影响。
function cssHelper(el, prototype) { for (let i in prototype) { el.style[i] = prototype[i] }}const item = document.createElement('div')item.innerHTML = '秋风的笔记'cssHelper(item, { position: 'absolute', top: `50px`, left: `50px`, fontSize: `16px`, color: '#000', lineHeight: 1.5, opacity: 0.1, transform: `rotate(-15deg)`, transformOrigin: '0 0', userSelect: 'none', whiteSpace: 'nowrap', overflow: 'hidden',})
有了一个水印片,我们就可以通过计算屏幕的宽高,以及水印的大小来计算我们需要生成的水印个数。const waterHeight = 100;const waterWidth = 180;const { clientWidth, clientHeight } = document.documentElement || document.body;const column = Math.ceil(clientWidth / waterWidth);const rows = Math.ceil(clientHeight / waterHeight);for (let i = 0; i < column * rows; i++) { const wrap = document.createElement('div'); cssHelper(wrap, Object.create({ position: 'relative', width: `${waterWidth}px`, height: `${waterHeight}px`, flex: `0 0 ${waterWidth}px`, overflow: 'hidden', })); wrap.appendChild(createItem()); waterWrapper.appendChild(wrap)}document.body.appendChild(waterWrapper)
这样子我们就完美地实现了上面我们给出的思路的样子啦。canvas
的实现很简单,主要是利用canvas
绘制一个水印,然后将它转化为 base64 的图片,通过canvas.toDataURL()
来拿到文件流的 url ,关于文件流相关转化可以参考我之前写的文章一文带你层层解锁「文件下载」的奥秘, 然后将获取的 url 填充在一个元素的背景中,然后我们设置背景图片的属性为重复。.watermark { position: fixed; top: 0px; right: 0px; bottom: 0px; left: 0px; pointer-events: none; background-repeat: repeat;}function createWaterMark() { const angle = -20; const txt = '秋风的笔记' const canvas = document.createElement('canvas'); canvas.width = 180; canvas.height = 100; const ctx = canvas.getContext('2d'); ctx.clearRect(0, 0, 180, 100); ctx.fillStyle = '#000'; ctx.globalAlpha = 0.1; ctx.font = `16px serif` ctx.rotate(Math.PI / 180 * angle); ctx.fillText(txt, 0, 50); return canvas.toDataURL();}const watermakr = document.createElement('div');watermakr.className = 'watermark';watermakr.style.backgroundImage = `url(${createWaterMark()})`document.body.appendChild(watermakr);
function createWaterMark() { const svgStr = `<svg xmlns="http://www.w3.org/2000/svg" width="180px" height="100px"> <text x="0px" y="30px" dy="16px" text-anchor="start" stroke="#000" stroke-opacity="0.1" fill="none" transform="rotate(-20)" font-weight="100" font-size="16" > 秋风的笔记 </text> </svg>`; return `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svgStr)))}`;}const watermakr = document.createElement('div');watermakr.className = 'watermark';watermakr.style.backgroundImage = `url(${createWaterMark()})`document.body.appendChild(watermakr);
Chrome Devtools
找到对应的元素,直接按 delete
即可删除。MutationObserver
,能够监控元素的改动。const targetNode = document.getElementById('some-id');// 观察器的配置(需要观察什么变动)const config = { attributes: true, childList: true, subtree: true };// 当观察到变动时执行的回调函数const callback = function(mutationsList, observer) { // Use traditional 'for loops' for IE 11 for(let mutation of mutationsList) { if (mutation.type === 'childList') { console.log('A child node has been added or removed.'); } else if (mutation.type === 'attributes') { console.log('The ' + mutation.attributeName + ' attribute was modified.'); } }};// 创建一个观察器实例并传入回调函数const observer = new MutationObserver(callback);// 以上述配置开始观察目标节点observer.observe(targetNode, config);
而MutationObserver
主要是监听子元素的改动,因此我们的监听对象为 document.body
, 一旦监听到我们的水印元素被删除,或者属性修改,我们就重新生成一个。通过以上示例,加上我们的思路,很快我们就写一个监听删除元素的示例。(监听属性修改也是类似就不一一展示了)// 观察器的配置(需要观察什么变动)const config = { attributes: true, childList: true, subtree: true };// 当观察到变动时执行的回调函数const callback = function (mutationsList, observer) {// Use traditional 'for loops' for IE 11 for (let mutation of mutationsList) { mutation.removedNodes.forEach(function (item) { if (item === watermakr) { document.body.appendChild(watermakr); } }); }};// 监听元素const targetNode = document.body;// 创建一个观察器实例并传入回调函数const observer = new MutationObserver(callback);// 以上述配置开始观察目标节点observer.observe(targetNode, config);
我们打开控制台来检验一下。Chrome Devtools
,点击设置 - Debugger - Disabled JavaScript .delete
我们的水印元素。charles
,将生成水印相关的代码删除。Ctrl + F
搜索 watermark
相关字眼。(这一步是作为一个程序员的直觉,基本上你要找什么,搜索对应的英文就可以 ~)MutationObserver
方法。我们使用我们的第一个破解方法,将 JavaScript 禁用,再将元素删除。暗水印是一种肉眼不可见的水印方式,可以保持图片美观的同时,保护你的资源版权。暗水印的生成方式有很多,常见的为通过修改RGB 分量值的小量变动、DWT、DCT 和 FFT 等等方法。
canvas
标签。<canvas id="canvas" width="256" height="256"></canvas>var ctx = document.getElementById('canvas').getContext('2d');var img = new Image();var originalData;img.onload = function () { // canvas像素信息 ctx.drawImage(img, 0, 0); originalData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); console.log() processData(ctx, originalData)};img.src = 'qiufeng-super.png';
我们打印出这个数组,会有一个非常大的数组,一共有 256 * 256 * 4 = 262144 个值。因为每个像素除了 RGB 外还有一个 alpha 通道,也就是我们常用的透明度。var processData = function (ctx, originalData) { var data = originalData.data; for (var i = 0; i < data.length; i++) { if (i % 4 == 0) { // R分量 if (data[i] % 2 == 0) { data[i] = 0; } else { data[i] = 255; } } else if (i % 4 == 3) { // alpha通道不做处理 continue; } else { // 关闭其他分量,不关闭也不影响答案 data[i] = 0; } } // 将结果绘制到画布 ctx.putImageData(originalData, 0, 0);}processData(ctx, originalData)
解密完会出现类似于以下这个样子。var textData;var ctx = document.getElementById('canvas').getContext('2d');ctx.font = '30px Microsoft Yahei';ctx.fillText('秋风的笔记', 60, 130);textData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data;
然后提取加密信息在待加密的图片上进行处理。var mergeData = function (ctx, newData, color, originalData) { var oData = originalData.data; var bit, offset; // offset的作用是找到alpha通道值,这里需要大家自己动动脑筋 switch (color) { case 'R': bit = 0; offset = 3; break; case 'G': bit = 1; offset = 2; break; case 'B': bit = 2; offset = 1; break; } for (var i = 0; i < oData.length; i++) { if (i % 4 == bit) { // 只处理目标通道 if (newData[i + offset] === 0 && (oData[i] % 2 === 1)) { // 没有信息的像素,该通道最低位置0,但不要越界 if (oData[i] === 255) { oData[i]--; } else { oData[i]++; } } else if (newData[i + offset] !== 0 && (oData[i] % 2 === 0)) { // // 有信息的像素,该通道最低位置1,可以想想上面的斑点效果是怎么实现的 oData[i]++; } } } ctx.putImageData(originalData, 0, 0);}
主要的思路还是我一开始所讲的,在有像素信息的点,将 R 偶数的通道+1。在没有像素点的地方将 R 通道转化成偶数,最后在 img.onload
调用 processData(ctx, originalData)
。img.onload = function () { // 获取指定区域的canvas像素信息 ctx.drawImage(img, 0, 0); originalData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height); console.log(originalData) processData(ctx, originalData)};
以上方法就是一种比较简单的加密方式。以上代码都放到了仓库 watermark/demo/canvas-dark-watermark.html
路径下,方法都封装好了~。const { RPCClient } = require("@alicloud/pop-core");var client = new RPCClient({ endpoint: "http://imm.cn-shenzhen.aliyuncs.com", accessKeyId: 'xxx', accessKeySecret: 'xxx', apiVersion: "2017-09-06",});(async () => { try { var params = { Project: "test-project", ImageUri: "oss://watermark-shenzheng/source/20201009-182331-fd5a.png", TargetUri: "oss://watermark-shenzheng/dist/20201009-182331-fd5a-out.jpg", Model: "DWT" }; var result = await client.request("DecodeBlindWatermark", params); console.log(result); } catch (err) { console.log(err); }})()
我们写了一个demo进行了测试。由于阿里云含有多种暗水印加密方式,为啥我使用了 DWT
呢?因为其他几种都需要原图,而我们刚才的测试,他上传只会上传一个文件到 OSS ,因此大致上排除了需要原图的方案。dom-to-image
这个库,在前端直接进行下载,或者使用截图的方式。目前通过直接下载和通过站点内生成,发现元素略有不同。dom-to-image
的方式下载,第二种为站点内下载,明显大了一些。(有点怀疑他在图片生成中可能做了什么手脚)DWT
暗水印进行的加密,解密后的样子为"秋风"字样,我们分别来测试一下。关键词:水印,教程,详细,设计