「前端添加水印」你真的了解全面嗎?
## 引言:水印背后的秘密
在Web前端開發中,水印是一種常見且重要的功能,它用于保護版權、防止惡意截圖傳播以及增加品牌曝光度。然而,實現前端水印的方式多種多樣,其中蘊含的技術細節你是否真正掌握全面了呢?本文將深入探討前端水印的實現原理、應用場景、不同方法的優劣對比,并通過具體代碼實例呈現給各位讀者。
### **一、前端水印基礎概念與應用場景**
**1. 水印類型及其作用**
- **靜態水印**:固定位置和內容的水印,常用于頁眉、頁腳或者背景層。
- **動態水印**:可以根據用戶信息動態生成,例如用戶ID、訪問時間等,用于追蹤和防偽。
**2. 常見應用場景**
- **版權保護**:網站內容、圖片或視頻上添加版權聲明,表明所有權。
- **用戶行為跟蹤**:針對用戶操作添加個性化的動態水印,如下載報告、查看數據時記錄用戶身份。
- **內部測試階段標識**:在產品預覽或測試版本中添加“測試版”或“機密”等字樣,防止信息泄露。
### **二、前端水印實現技術解析**
**1. CSS樣式實現**
利用CSS `::before` 和 `::after` 偽元素創建半透明文字或圖片作為水印,適用于靜態水印。
```html
<style>
.watermark {
position: relative;
}
.watermark::before {
content: "Copyright ? Your Company";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
color: rgba(255, 255, 255, 0.5);
font-size: 20px;
font-weight: bold;
background-image: url('watermark.png');
background-repeat: repeat;
mix-blend-mode: multiply;
pointer-events: none;
z-index: -1;
}
</style>
<div class="watermark">
<!-- 頁面主要內容 -->
</div>
```
**2. JavaScript動態生成**
借助JavaScript來動態創建DOM元素,可以靈活控制水印的位置、內容和樣式,尤其適合動態水印。
```javascript
function addWatermark(text, opacity) {
const watermark = document.createElement('div');
watermark.style.position = 'absolute';
watermark.textContent = text;
watermark.style.color = `rgba(0, 0, 0, ${opacity})`;
watermark.style.fontSize = '24px';
watermark.style.zIndex = 9999;
// 動態計算水印位置,這里僅舉例居中顯示
const pageWidth = window.innerWidth;
const pageHeight = window.innerHeight;
watermark.style.left = `${(pageWidth / 2) - (text.length * 12)}px`;
watermark.style.top = `${(pageHeight / 2)}px`;
document.body.appendChild(watermark);
}
addWatermark('User ID: uniqueID', 0.5);
```
**3. Canvas繪圖法**
對于圖片水印或復雜效果,可以利用HTML5 Canvas API在圖片加載后直接在畫布上繪制水印,再轉換為圖片輸出。
```javascript
let canvas = document.createElement('canvas');
let ctx = canvas.getContext('2d');
// 加載原始圖片
let img = new Image();
img.src = 'original.jpg';
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
// 繪制原始圖片
ctx.drawImage(img, 0, 0);
// 繪制水印文字
ctx.font = '24px Arial';
ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
ctx.fillText('Your Watermark Text', 10, 10);
// 將帶有水印的canvas轉為圖片
let dataUrl = canvas.toDataURL('image/jpeg');
let watermarkedImg = new Image();
watermarkedImg.src = dataUrl;
document.body.appendChild(watermarkedImg);
};
```
### **三、前端水印方案選擇與優劣分析**
**1. CSS樣式 vs JavaScript動態生成**
- **CSS樣式**:優點在于簡單易用,無需額外引入JavaScript;缺點是靈活性較低,不適用于動態水印。
- **JavaScript動態生成**:優點是高度靈活,可應對各種復雜的動態場景;缺點是對SEO友好度相對較低,且可能影響頁面加載速度。
**2. Canvas繪圖法**
- **Canvas繪圖**:適用于圖片類資源的水印添加,既能保證原圖質量,又能精確控制水印位置和樣式;但需要注意兼容性問題,尤其是老版本IE瀏覽器可能不支持。
### **四、前沿探索:Web Workers與WebGL在水印技術中的應用**
隨著Web技術的進步,Web Workers可以用來實現后臺線程繪制水印,避免阻塞主線程;而WebGL則能實現更復雜三維效果的水印,比如傾斜、旋轉等,從而擴展前端水印的可能性。
### **結語:**
前端水印技術并非止步于簡單的文字疊加,而是涵蓋了樣式布局、DOM操作、圖形繪制等多個層面的知識點。熟練掌握這些技術,不僅能夠有效保障網站內容的安全性和版權,更能提升用戶體驗,展現獨特的品牌形象。在面對不同的業務需求時,合理選擇并靈活運用前端水印技術顯得尤為重要。持續關注這一領域的發展,方能在實戰中游刃有余,從容應對挑戰。
者:fransli,騰訊 PCG 前端開發工程師
Web 水印技術在信息安全和版權保護等領域有著廣泛的應用,對防止信息泄露或知識產品被侵犯有重要意義。水印根據可見性可分為可見水印和不可見水印(盲水印),本文將分別予以介紹,帶你探秘 web 水印技術。
一種比較常見的簡單水印場景是給文章、表格加上 logo 水印,用以申明版權。
這里想要的效果就是一個淺淺的 logo 平鋪展示。實現起來也比較簡單,只需制作一個半透明的 logo 圖片,設為文章或者表格的背景圖片即可。僅需一行 CSS 聲明。
background-image:url("./logo.png");
實現圖片平鋪關鍵的 CSS 屬性是 background-repeat,值為 repeat 時是平鋪,這也是它的默認值,所以可以省略。
照葫蘆畫瓢,如果要給整個 Web 頁面加上水印,是不是給頁面的 body 元素設置背景圖片平鋪展示就可以了呢?
然而通常并不會這么處理,因為文章和表格內容多以文本為主,不會明顯遮擋水印,而一個完整的頁面往往還包含很多其他頁面元素,比如圖片、視頻、控件等等,它們很可能會遮擋住背景圖片,從而影響水印效果。
所以,為了避免被其他元素遮擋,針對頁面的水印一般會使用一個層級比較高且覆蓋整個頁面的元素來承載。
div.watermark{
position: fixed;
left:0;
top:0;
width: 100vw;
height: 100vh;
background-image:url("./logo.png");
opacity: .5;
z-index: 3000;
}
這樣一來,其他元素就遮擋不住水印了。不過,這個 div 反過來可能會遮擋頁面其他元素,影響頁面元素操作。還需要一條關鍵的 CSS 聲明來破解這個問題 :
pointer-events: none;
這個 CSS 聲明會使該元素“可穿透”,“看得見、摸不著”,不再影響頁面操作。
很多時候,給頁面加水印的目的并不是申明版權,而是為了支持溯源。此時水印的內容并不會只是一個 logo,通常會包含用戶信息,比如用戶名、UID、手機號等等。
這就意味著,每個用戶的水印內容是不同的,無法通過提前準備好一張圖片來滿足了。這種場景往往需要根據用戶信息動態生成圖片。
我們來看下幾種主流的動態生成水印圖片的方式:
傳統的方式是在服務端生成圖片。頁面上發起的圖片請求中可以附帶用戶信息,服務端根據這些參數動態生成圖片,并將圖片數據作為該請求的響應返給頁面,頁面拿到后將其用作水印。
這種方式的優點是兼容性好,缺點是需要前后端配合,增加了頁面請求和服務端資源開銷,防攻擊能力也較差。
HTML5 引入 Canvas 特性使得瀏覽器自身具備了繪圖能力。經過多年的發展,主流瀏覽器基本都已可以提供良好的支持。通過 Canvas 可以輕松繪制圖片,并可將圖片數據導出,用于頁面圖片或背景。
const canvasElement = document.createElement('canvas');
const context = canvasElement.getContext('2d');
canvasElement.width = 200;
canvasElement.height = 200;
context.rotate((-30 * Math.PI) / 180);
context.font = '400 26px Arial';
context.fillStyle = '#B9C0CA';
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText('水印文字', 70, 130);
const watermark = canvasElement.toDataURL('image/png');
通過上述示例代碼可拿到水印圖片的 data URI 數據,用作水印承載元素的背景圖片平鋪展示即可。
這種方式不需要服務端配合,在前端就可以完成,且有助于減少請求和服務端資源開銷。曾經面臨的瀏覽器兼容問題現在也不再是問題,該方案已逐漸流行起來。
對于純文字的水印來說,有沒有辦法不生成圖片而直接實現平鋪呢?
動態創建大量 DOM 節點,通過 CSS 控制排列當然可以實現,但是繁瑣且性能差,優雅更無從談起。
不妨換個角度思考,有沒有辦法讓文字不轉成圖片就可以用作 background-image 屬性的值呢?這樣就可以利用 background-repeat 實現平鋪效果了。
這時候可以考慮使用 SVG,因為 SVG 具有文本和圖像的雙重特性。看上去是文本,然而在很多場景可以當做圖片使用。
我們可以通過 SVG 的相關屬性精準控制字體位置、大小、顏色、透明度和旋轉角度等參數。如:
<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<text x="50%" y="50%" font-size="30" fill="#a2a9b6" fill-opacity="0.3" font-family="system-ui, sans-serif" text-anchor="middle" dominant-baseline="middle" transform='rotate(-45, 100 100)'>水印文字</text>
</svg>
考慮到瀏覽器兼容性,用作背景圖片時,建議將 SVG 編碼為 Base64(或轉義特定字符):
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48dGV4dCB4PSI1MCUiIHk9IjUwJSIgZm9udC1zaXplPSIzMCIgZmlsbD0iI2EyYTliNiIgZmlsbC1vcGFjaXR5PSIwLjMiIGZvbnQtZmFtaWx5PSJzeXN0ZW0tdWksIHNhbnMtc2VyaWYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIHRyYW5zZm9ybT0ncm90YXRlKC00NSwgMTAwIDEwMCknPuawtOWNsOaWh+WtlzwvdGV4dD48L3N2Zz4=");
水印是用來保護信息安全的。信息要安全,首先要確保水印自身的安全,提高水印的防攻擊(篡改、刪除等)能力。
可見水印大都是基于 DOM 的,找到這個 DOM 節點,通過瀏覽器插件、抓包工具等在頁面上注入一段 JavaScript 或者 CSS 代碼對其進行篡改或刪除并不困難。
防止外部代碼篡改,一種思路是把水印元素封裝起來,與外部環境進行隔離。
在 Chrome 逐步統治瀏覽器江湖之后,谷歌正野心勃勃的推廣 Web Components 技術。該技術允許在 Web 中創建可重用的小部件或組件。組件化開發在前端業界已經流行相當長一段時間了,這主要得益于前端三大框架的推崇,但具體組件標準是由框架各自制定的,而 Web Components 可理解為 Web 標準化的組件。
Web Components 的一個重要特性就是“封裝”,即可以將標記結構、樣式和行為隱藏起來,并與頁面上的其他代碼相隔離。比如我們熟悉的 video 元素,它的進度條、按鈕等控件都已被封裝。
Shadow DOM 接口是“封裝”特性的關鍵所在,它可以將一個隱藏的、獨立的 DOM 附加到一個元素上。
為了提高 web 水印的隱蔽性,同時避免受外部代碼影響,從而在一定程度上防止篡改,可以考慮把水印元素放在 Shadow DOM 中。
來看下 Shadow DOM 的基本用法。使用 Element.attachShadow() 方法來將一個 shadow root 附加到任何一個元素上。它接受一個配置對象作為參數,該對象有一個 mode 屬性,值可以是 open 或者 closed 。open 表示可以通過頁面內的 JavaScript 方法來獲取 Shadow DOM。而 closed 則表示不可以從外部獲取 Shadow DOM 。
Element.attachShadow({mode: 'closed'});
樣式怎么隔離呢?Shadow DOM 中的樣式本身就是隔離的,除非主動使用 CSS 變量、part 屬性等暴露,外部樣式是不會影響到組件內的。
Shadow DOM 提高了水印的隱蔽性,同時可以防止外部代碼修改。除此之外,還有一種常見的攻擊場景——人為修改,比如在瀏覽器控制臺直接修改或刪除對應的 DOM 元素。
可以考慮“監聽”這種行為,一旦發生就馬上修復,比如重新插入一個。那怎么實現這種“監聽”呢?現代瀏覽器中有多種觀察者(Observer),比如IntersectionObserver、PerformanceObserver、ResizeObserver、ReportingObserver、MutationObserver 等。其中,MutationObserver 就可以用來監聽 DOM 變動,DOM 的任何變動,比如節點的增減、屬性的變動、文本內容的變動,通過該 API 都可以得到通知。
所以可以使用 MutationObserver API 來監聽水印元素 DOM 變化,一旦監聽到 DOM 元素被修改或者刪除,就立即重新插入一個。
不可見水印也叫盲水印、隱水印,顧名思義是一種看不到的水印,看不到還要它做什么呢?其實,不可見水印在一些對安全性要求較高的場景意義還是蠻大的。不可見水印通常具有比可見水印更好的隱蔽性和抗攻擊性。雖不可見,但通過一定的技術手段是可以將水印信息從其載體上提取出來的,這就使得其載體具備了溯源能力,在關鍵時刻往往能發揮大作用。
我總結不可見水印相對可見水印至少有以下三個明顯的優勢:
不可見水印(盲水印)屬于信息隱匿技術(也叫隱寫術),歷史悠久,手段繁多。在現代,隨著計算機網絡技術的發展,數字產品的信息安全和版權保護也已成為信息隱匿技術的一個重要課題。隱寫術在數字音頻、數字視頻和數字圖像領域有著非常廣泛的應用。
Web 上基于 DOM 的盲水印大都不靠譜,而另一方面數字圖像是信息隱藏和數字水印領域研究最多和最早的一種載體,相較于 Web,數字圖像領域有著更為成熟的數字水印算法。我們不妨先來看下數字圖像領域的常見盲水印技術。
在說數字水印之前,這里介紹一些數字圖像的基礎知識。
數字圖像(位圖)是由像素(pixel)組成。
通常,考慮到計算速度和性能,圖像處理和圖像識別大都會將圖像轉成灰度圖或者選擇其中一個通道進行。
如上文所述,灰度圖像的一個像素有 256 種狀態,通常用灰度值( 0-255 )表示,0 表示黑色,255 表示白色,灰度值越大表示亮度越高。
灰度可用一個字節,即 8 比特二進制數表示,其中最高位對圖像的貢獻最大,最低位對圖像的貢獻最小,稱為最低比特位(Least Significant Bit,LSB)。
如果將一個圖像所有像素的比特位抽出來,就構成了 8 個不同的位平面,從 LSB(最低有效位 0)到 MSB(最高有效位 7)。位平面從低位到高位,圖像的特征逐漸變得復雜,細節不斷增加,相鄰比特的相關性也越強。而比特位越低包含的圖像信息就越少,最低位平面類似于隨機噪聲。因此,改變低位對圖像的成像質量影響不大。
LSB 水印就是利用了這一點,用水印信息替換載體圖像的最低比特位,這樣原圖像的 7個高位平面就與表示水印信息的最低位平面組成了新的圖像。
LSB 水印魯棒性(防攻擊性)較差,水印信息容易被抹去。
將數字圖像用一個矩陣來表示,是圖像的空間域表示方法,LSB 就是在圖像的空間域隱藏信息,魯棒性較差。而在圖像信號的頻域(變換域)中隱藏信息要比在空間域中隱藏信息具有更好的魯棒性。那么如何把圖像信號從空間域轉換到頻域呢?這里就需要用到大名鼎鼎的 傅里葉變換 了。
法國數學家傅里葉大家一定不陌生,高數里就有傅里葉級數。
傅里葉提出的傅里葉變換(Fourier transform)理論,表示能將滿足一定條件的某個函數表示成三角函數(正弦和/或余弦函數)或者它們的積分的線性組合,可用于把信號從時間域(或空間域)變換到頻率域。
在此之前人們對信號的分析主要集中在空間域,傅里葉變換的提出為頻域分析奠定了基礎,有助于解決許多圖像的問題。
傅里葉變換在數字圖像處理領域有著極為重要的應用,圖像領域變換的實質是把圖像函數展開成具有不同空間頻率的正、余弦信號的疊加,也就是說任何圖像都可以分解為若干個頻率不同的亮度呈正弦變化的圖像之和。把圖像從空間域變換到頻率域后,就能夠實現對圖像數據進行不同頻率成分的提取。對于圖像信號來說,可以把灰度(亮度)看做頻率,傅里葉變換可作為圖像灰度值形成的空間域與其頻率域的橋梁。
在頻域中隱藏信息就是傅里葉變換在數字圖像處理領域的一個典型應用場景。通常多選擇在圖像頻域的中頻部分嵌入信息,因為高頻部分易于被各種信號處理方法破壞,而人的視覺又對低頻部分比較敏感,容易察覺低頻部分的變化。
在圖像頻域嵌入水印信息的大致流程為:把原始圖像通過離散傅里葉變換轉換到頻域(變換域),把水印文字信息混入,再經過離散傅里葉變換的逆變換,便得到了載有水印信息的圖像。水印信息隱匿性較好。
光說不練假把式,操練起來。
下圖是我隨手拍的鵝廠北京總部大樓一角。
對上圖的一個通道進行離散傅里葉變換,在其變換域(頻域)加入水印文字(fransli)后,再進行離散傅里葉變換的逆變換,便得到了下圖。怎么樣,看不到水印信息吧?
對上圖進行離散傅里葉變換,將圖片轉換到頻域(變換域),我們可以清楚的看到嵌入的水印文字(下圖)。
頻域盲水印具有比較好的防攻擊性,我們來測試一下。
我們截取圖像中的一部分并重新采樣,然后嘗試提取水印信息。
可以看到還是有很大概率可以提取到有效水印信息的。
上面介紹了幾種常見的不可見水印(盲水印)實現方式,其中魯棒性比較好的是基于頻域的數字圖像盲水印,但這種水印主要是針對數字圖像,而 Web 上的內容載體并不一定都是圖片,常見的需要加水印的載體除了圖片還有文本、表格等,這些場景該如何應用頻域盲水印呢?
或許,Canvas 就是答案。
atermark.js 是一個 JavaScript 框架用于以批量的方式對圖片插入水印。它利用 HTML5 canvas 標簽實現,只要為圖片添加特定的 class 就能添加水印。采用 ES6 編寫而成,并通過 Babel 提供給當前的瀏覽器。支持網址、文件輸入、blob 和頁面圖像。
任何支持 File 和 FileReader 的瀏覽器都應該可以工作。
https://github.com/pa7/watermark.js
*請認真填寫需求信息,我們會在24小時內與您取得聯系。