有個web項目要有水印需求,后端可以提供文本或者圖片。
參考網(wǎng)絡(luò)上的前端水印方案,目前選擇的是獲取文本,通過文本生成svg渲染水印。
1. svg生成文本水印
參考文章6. 前端水印生成方案 的svg方案是ES6語法的,項目問題,我把它改寫成ES5語法了。
js代碼如下:
/* svg 實現(xiàn)水印 */
function wmSvg(wmText, container) {
if (!wmText || typeof wmText !== 'string') return;
var svgStr = '<svg xmlns="http://www.w3.org/2000/svg" width="300px" height="200px"> <text x="50%" y="50%" dy="12px" text-anchor="middle" stroke="#000000" stroke-width="1" stroke-opacity="0.2" fill="none" transform="rotate(-45, 120 120)" style="font-size: 20px;"> '+ wmText +'</text></svg>';
var base64Url = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svgStr)));
var __wm = document.querySelector('.__wm');
var wmDiv = __wm || document.createElement("div");
wmDiv.className = '_wm';
wmDiv.setAttribute('style', 'position:absolute;top:0;left:0;width:100%;height:100%;z-index:2000;pointer-events:none;background-repeat;background-image:url('+base64Url+');');
if(!container) container = document.body;
container.style.position = 'relative';
container.insertBefore(wmDiv, container.firstChild);
}
// 調(diào)用方法,生成水印
wmSvg('Celine~');
渲染效果:

image.png
2. 動態(tài)計算svg的寬高
本次項目中的水印文字是由后端提供的,水印文字是由用戶自定義。再上面的代碼中,生成的svg寬高是寫死的300*200px,如果文字太長,這個寬度不夠容納就會有遮擋效果,所以自己用粗略估算的方式,動態(tài)計算了svg的寬高。
js代碼如下:
/* svg 實現(xiàn)水印 */
function wmSvg(wmText, container) {
if (!wmText || typeof wmText !== 'string') return;
var fontSize = 16,
rotateAngle = 30,
textLen = wmText.length, // 字符串長度(中文雙字符和英文單字符都是按照一個字符計算)
// textWidth = Math.ceil((textLen -20)*fontSize) + 200, // 時間戳是必須項的計算方式
textWidth = Math.ceil(textLen *fontSize), // 通用版的計算方式
textWidth = textWidth < 300 ? 300 : textWidth, // 設(shè)置最小長度,不然文字少水印太密集。
svgWidth = Math.ceil(textWidth * Math.cos(Math.PI/180*rotateAngle)),
svgHeight = Math.ceil(textWidth * Math.sin(Math.PI/180*rotateAngle)),
rotateX = Math.floor(svgWidth/2),
rotateY = Math.floor(svgHeight/2);
console.log(textLen, textWidth);
console.log(svgWidth,svgHeight);
// 計算規(guī)則說明:
// 本次項目的水印格式是:“用戶名 自定義內(nèi)容 時間戳” ,比如“celine 個人博客 2023-08-04 12:00:00”
// 其中,時間戳 -> 單字符,必須要有,拿出來單獨計算下。大致占20個字符長度,渲染寬度是150px左右,此處取寬容值200px。(若沒有必須的時間戳,可以用通用計算方式)
// 自定義內(nèi)容 -> 默認(rèn)為中文雙字符(取寬容場景)。渲染寬度和字體大小相關(guān),所以此處粗略用字符長度*字體大小。
// svg的寬高是根據(jù)文本長度結(jié)合選擇角度,根據(jù)直角三角形的勾股定理做計算
// rotate 的旋轉(zhuǎn)中心點偏移XY,根據(jù)svg寬高取一半。
var svgStr = '<svg xmlns="http://www.w3.org/2000/svg" width="'+svgWidth+'px" height="'+svgHeight+'px"> <text x="50%" y="50%" dy="12px" text-anchor="middle" stroke="#000000" stroke-width="1" stroke-opacity="0.2" fill="none" transform="rotate(-'+rotateAngle+', '+rotateX+' ,'+rotateY+')" style="font-size: '+fontSize+'px;"> '+ wmText +'</text></svg>';
var base64Url = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svgStr)));
var __wm = document.querySelector('._wm');
var wmDiv = __wm || document.createElement("div");
wmDiv.className = '_wm';
wmDiv.setAttribute('style', 'position:absolute;top:0;left:0;width:100%;height:100%;z-index:2000;pointer-events:none;background-repeat:repeat;background-image:url('+base64Url+');');
if(!container) container = document.body;
container.style.position = 'relative';
container.insertBefore(wmDiv, container.firstChild);
}
// 調(diào)用方法,生成水印
// wmSvg('Celine~');
wmSvg('Celine 我的博客是有個性的不要隨便惹毛它哦 2023-08-04 12:00:00');

效果圖
3. 如何防止控制臺刪水印呢?
這個就可以用到new MutationObserver()這個東西了。這個方法的詳解,可以看這篇 csdn:MutationObserver詳解,
下面直接貼demo的代碼:
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<div>
<p>我是頁面內(nèi)容,哈哈哈</p>
<div style="width: 450px;height: 800px;background: pink;">我是有指定寬高的div,我是來占大位的,哈哈哈</div>
</div>
<script type="text/javascript">
// 調(diào)用方法,生成水印
// wmSvg('Celine~');
wmSvg('Celine~ 2023-08-04 12:00:00');
// wmSvg('Celine 我的博客是有個性的不要隨便惹毛它哦我的博客是有個性的不要隨便惹毛它哦我的博客是有個性的不要隨便惹毛它哦我的博客是有個性的不要隨便惹毛它哦我的博客是有個性的不要隨便惹毛它哦我的博客是有個性的不要隨便惹毛它哦 2023-08-04 12:00:00');
/* svg 實現(xiàn)水印 */
function wmSvg(wmText, container) {
if (!wmText || typeof wmText !== 'string') return;
var fontSize = 16,
rotateAngle = 30,
textLen = wmText.length, // 字符串長度(中文雙字符和英文單字符都是按照一個字符計算)
// textWidth = Math.ceil((textLen -20)*fontSize) + 200, // 時間戳是必須項的計算方式
textWidth = Math.ceil(textLen *fontSize), // 通用版的計算方式
textWidth = textWidth < 300 ? 300 : textWidth, // 設(shè)置最小長度,不然文字少水印太密集。
textWidth = textWidth > 600 ? 600 : textWidth, // 設(shè)置最大長度,不然文字多水印跑了。
svgWidth = Math.ceil(textWidth * Math.cos(Math.PI/180*rotateAngle)),
svgHeight = Math.ceil(textWidth * Math.sin(Math.PI/180*rotateAngle)),
rotateX = Math.floor(svgWidth/2)-50,
rotateY = Math.floor(svgHeight/2)+50;
var svgStr = '<svg xmlns="http://www.w3.org/2000/svg" width="'+svgWidth+'px" height="'+svgHeight+'px"> <text x="50%" y="50%" dy="12px" text-anchor="middle" stroke="#000000" stroke-width="1" stroke-opacity="0.2" fill="none" transform="rotate(-'+rotateAngle+', '+rotateX+' ,'+rotateY+')" style="font-size: '+fontSize+'px;"> '+ wmText +'</text></svg>';
createWm(svgStr, container);
}
/* 抽離生成base64和填充水印功能,單獨一個方法,便于后面的監(jiān)聽復(fù)用。*/
function createWm(svgStr, container){
var base64Url = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(svgStr)));
if (document.querySelector('._wm')) return; // 已有水印直接退出。
// var wmDiv = document.querySelector('._wm') || document.createElement("div"); // 這種方式會導(dǎo)致監(jiān)聽死循環(huán),因為新水印的svgStr和頁面上不一致,會一直觸發(fā)監(jiān)聽。
var wmDiv = document.createElement("div");
wmDiv.className = '_wm';
var wmStyleStr = 'position:absolute;top:0;left:0;width:100%;height:100%;z-index:2000;pointer-events:none;background-repeat:repeat;background-image:url('+base64Url+');'
wmDiv.setAttribute('style', wmStyleStr);
if(!container) container = document.body;
container.style.position = 'relative';
container.insertBefore(wmDiv, container.firstChild);
wmObserver(svgStr, container, wmStyleStr);
}
/* 水印監(jiān)聽 */
function wmObserver(svgStr, container, wmStyleStr){
const options = {
childList: true,
attributes: true,
characterData: true,
subtree: true,
attributeOldValue: true,
characterDataOldValue: true
}
// console.log(window.MutationObserver, window.WebKitMutationObserver, window.MozMutationObserver );
var mutation = new MutationObserver(function(mutationRecoards, observer){
console.log(mutationRecoards, observer);
// 沒有水印時
var wmDiv = document.querySelector('._wm');
if (!wmDiv) {
createWm(svgStr, container);
return;
}
// 水印樣式被修改時
if (wmDiv.getAttribute('style') != wmStyleStr) {
wmDiv.setAttribute('style', wmStyleStr);
}
});
// 安裝監(jiān)聽
mutation.observe(container, options);
}
</script>
</body>
</html>
4. 真實打印呢?
這是我目前遇到的困境,前面的方案頁面打印機(jī)打印除了是沒有水印的。。
誰有什么好方案再告知下。
參考文章
-
svg文本<text>詳解
挺詳細(xì)的一篇講解,有代碼有效果圖。其中有多行文字的svg展示代碼。我是因為要設(shè)置水印樣式查看了該文章。 -
JavaScript中的三角函數(shù)
簡略回顧了下三角函數(shù)。主要是要看下Js中的Math.sin()里面填入的角度要怎么設(shè)置。(舉例:30°的話,Math.sin(Math.PI/180*30)) -
jquery.watermark.js 在網(wǎng)頁中添加水印,打印時水印背景不見了,辦法來了
號稱可以通過print-color-adjust:exact;來改變打印時沒有水印的情況。但我自己的代碼(沒有使用jquery.watermark.js的)初步嘗試,真實打印機(jī)打印處理依舊沒有。后續(xù)再探究下。 -
Js和Canvas實現(xiàn)水印且控制臺不可刪除
防止控制臺刪水印那段可以好好借鑒下。
-
【W(wǎng)eb技術(shù)】談?wù)勊崿F(xiàn)的幾種方式
沒認(rèn)真看,有需要再來看。 -
前端水印生成方案
svg方案參考的文章,里面有canvas方案(HTMLCanvasElement.toDataURL)。 -
個人博客krryblog:頁面水印的實現(xiàn)以及防刪除方案
一個畢業(yè)去京東,現(xiàn)在在騰訊的前端小大佬?還是很佩服他的強(qiáng)迫癥的,哈哈哈。這篇文章很詳細(xì)的給出的代碼方案,值得借鑒。 - csdn:MutationObserver詳解
