時(shí)間:2023-07-21 09:42:01 | 來源:網(wǎng)站運(yùn)營
時(shí)間:2023-07-21 09:42:01 來源:網(wǎng)站運(yùn)營
從破解某定設(shè)計(jì)網(wǎng)站談前端水印(詳細(xì)教程):https://github.com/hua1995116/node-demo/tree/master/watermark
水印(watermark)是一種容易識(shí)別、被夾于紙內(nèi),能夠透過光線穿過從而顯現(xiàn)出各種不同陰影的技術(shù)。水印的類型有很多,有一些是整圖覆蓋在圖層上的水印,還有一些是在角落。
pointer-events
。當(dāng)它的被設(shè)置為pointer-events
CSS 屬性指定在什么情況下 (如果有) 某個(gè)特定的圖形元素可以成為鼠標(biāo)事件的 target。
none
的時(shí)候,能讓元素實(shí)體虛化,雖然存在這個(gè)元素,但是該元素不會(huì)觸發(fā)鼠標(biāo)事件。詳情可以查看 CSS3 pointer-events:none應(yīng)用舉例及擴(kuò)展 ? 張鑫旭-鑫空間-鑫生活 。// 文本內(nèi)容<div class="app"> <h1>秋風(fēng)</h1> <p>hello</p></div>
首先我們來生成一個(gè)水印塊,就是上面的 一個(gè)個(gè)秋風(fēng)的筆記
。這里主要有一點(diǎn)就是設(shè)置一個(gè)透明度(為了讓水印看起來不是那么明顯,從而不遮擋我們的主要頁面),另一個(gè)就是一個(gè)旋轉(zhuǎn),如果是正的水平會(huì)顯得不是那么好看,最后一點(diǎn)就是使用 userSelect
屬性,讓此時(shí)的文字無法被選中。CSS 屬性user-select
控制用戶能否選中文本。除了文本框內(nèi),它對被載入為 chrome 的內(nèi)容沒有影響。
function cssHelper(el, prototype) { for (let i in prototype) { el.style[i] = prototype[i] }}const item = document.createElement('div')item.innerHTML = '秋風(fēng)的筆記'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',})
有了一個(gè)水印片,我們就可以通過計(jì)算屏幕的寬高,以及水印的大小來計(jì)算我們需要生成的水印個(gè)數(shù)。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)
這樣子我們就完美地實(shí)現(xiàn)了上面我們給出的思路的樣子啦。canvas
的實(shí)現(xiàn)很簡單,主要是利用canvas
繪制一個(gè)水印,然后將它轉(zhuǎn)化為 base64 的圖片,通過canvas.toDataURL()
來拿到文件流的 url ,關(guān)于文件流相關(guān)轉(zhuǎn)化可以參考我之前寫的文章一文帶你層層解鎖「文件下載」的奧秘, 然后將獲取的 url 填充在一個(gè)元素的背景中,然后我們設(shè)置背景圖片的屬性為重復(fù)。.watermark { position: fixed; top: 0px; right: 0px; bottom: 0px; left: 0px; pointer-events: none; background-repeat: repeat;}function createWaterMark() { const angle = -20; const txt = '秋風(fēng)的筆記' 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" > 秋風(fēng)的筆記 </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
找到對應(yīng)的元素,直接按 delete
即可刪除。MutationObserver
,能夠監(jiān)控元素的改動(dòng)。const targetNode = document.getElementById('some-id');// 觀察器的配置(需要觀察什么變動(dòng))const config = { attributes: true, childList: true, subtree: true };// 當(dāng)觀察到變動(dòng)時(shí)執(zhí)行的回調(diào)函數(shù)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.'); } }};// 創(chuàng)建一個(gè)觀察器實(shí)例并傳入回調(diào)函數(shù)const observer = new MutationObserver(callback);// 以上述配置開始觀察目標(biāo)節(jié)點(diǎn)observer.observe(targetNode, config);
而MutationObserver
主要是監(jiān)聽子元素的改動(dòng),因此我們的監(jiān)聽對象為 document.body
, 一旦監(jiān)聽到我們的水印元素被刪除,或者屬性修改,我們就重新生成一個(gè)。通過以上示例,加上我們的思路,很快我們就寫一個(gè)監(jiān)聽刪除元素的示例。(監(jiān)聽屬性修改也是類似就不一一展示了)// 觀察器的配置(需要觀察什么變動(dòng))const config = { attributes: true, childList: true, subtree: true };// 當(dāng)觀察到變動(dòng)時(shí)執(zhí)行的回調(diào)函數(shù)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); } }); }};// 監(jiān)聽元素const targetNode = document.body;// 創(chuàng)建一個(gè)觀察器實(shí)例并傳入回調(diào)函數(shù)const observer = new MutationObserver(callback);// 以上述配置開始觀察目標(biāo)節(jié)點(diǎn)observer.observe(targetNode, config);
我們打開控制臺(tái)來檢驗(yàn)一下。Chrome Devtools
,點(diǎn)擊設(shè)置 - Debugger - Disabled JavaScript .delete
我們的水印元素。charles
,將生成水印相關(guān)的代碼刪除。Ctrl + F
搜索 watermark
相關(guān)字眼。(這一步是作為一個(gè)程序員的直覺,基本上你要找什么,搜索對應(yīng)的英文就可以 ~)MutationObserver
方法。我們使用我們的第一個(gè)破解方法,將 JavaScript 禁用,再將元素刪除。暗水印是一種肉眼不可見的水印方式,可以保持圖片美觀的同時(shí),保護(hù)你的資源版權(quán)。暗水印的生成方式有很多,常見的為通過修改RGB 分量值的小量變動(dòng)、DWT、DCT 和 FFT 等等方法。
canvas
標(biāo)簽。<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';
我們打印出這個(gè)數(shù)組,會(huì)有一個(gè)非常大的數(shù)組,一共有 256 * 256 * 4 = 262144 個(gè)值。因?yàn)槊總€(gè)像素除了 RGB 外還有一個(gè) 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 { // 關(guān)閉其他分量,不關(guān)閉也不影響答案 data[i] = 0; } } // 將結(jié)果繪制到畫布 ctx.putImageData(originalData, 0, 0);}processData(ctx, originalData)
解密完會(huì)出現(xiàn)類似于以下這個(gè)樣子。var textData;var ctx = document.getElementById('canvas').getContext('2d');ctx.font = '30px Microsoft Yahei';ctx.fillText('秋風(fēng)的筆記', 60, 130);textData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height).data;
然后提取加密信息在待加密的圖片上進(jìn)行處理。var mergeData = function (ctx, newData, color, originalData) { var oData = originalData.data; var bit, offset; // offset的作用是找到alpha通道值,這里需要大家自己動(dòng)動(dòng)腦筋 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) { // 只處理目標(biāo)通道 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,可以想想上面的斑點(diǎn)效果是怎么實(shí)現(xiàn)的 oData[i]++; } } } ctx.putImageData(originalData, 0, 0);}
主要的思路還是我一開始所講的,在有像素信息的點(diǎn),將 R 偶數(shù)的通道+1。在沒有像素點(diǎn)的地方將 R 通道轉(zhuǎn)化成偶數(shù),最后在 img.onload
調(diào)用 processData(ctx, originalData)
。img.onload = function () { // 獲取指定區(qū)域的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); }})()
我們寫了一個(gè)demo進(jìn)行了測試。由于阿里云含有多種暗水印加密方式,為啥我使用了 DWT
呢?因?yàn)槠渌麕追N都需要原圖,而我們剛才的測試,他上傳只會(huì)上傳一個(gè)文件到 OSS ,因此大致上排除了需要原圖的方案。dom-to-image
這個(gè)庫,在前端直接進(jìn)行下載,或者使用截圖的方式。目前通過直接下載和通過站點(diǎn)內(nèi)生成,發(fā)現(xiàn)元素略有不同。dom-to-image
的方式下載,第二種為站點(diǎn)內(nèi)下載,明顯大了一些。(有點(diǎn)懷疑他在圖片生成中可能做了什么手腳)DWT
暗水印進(jìn)行的加密,解密后的樣子為"秋風(fēng)"字樣,我們分別來測試一下。關(guān)鍵詞:水印,教程,詳細(xì),設(shè)計(jì)
客戶&案例
營銷資訊
關(guān)于我們
微信公眾號(hào)
版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。