水印項目咱們提出了兩種解決方案
1、用shadow dom實現javascript
一、基本思路java
經過 attachShadow 這個方法生成一個shadow root 即shadow的根節點,而後在這個根節點下面經過循環語句添加水印,利用position爲absolute進行排版,使其鋪滿容器node
show me the code:jquery
(function (root, factory) { if (typeof define === 'function' && define.amd) { /*AMD. Register as an anonymous module. *define([], factory); */ define([], factory()); } else if (typeof module === 'object' && module.exports) { /*Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node.*/ module.exports = factory(); } else { /*Browser globals (root is window)*/ root['watermark'] = factory(); } }(this, function () { /*Just return a value to define the module export.*/ let watermark = {}; /*加載水印*/ let loadMark = function () { let defaultSettings = { watermark_id: 'wm_div_id', //水印整體的id watermark_prefix: 'mask_div_id', //小水印的id前綴 watermark_txt: "測試水印", //水印的內容 watermark_x: 20, //水印起始位置x軸座標 watermark_y: 40, //水印起始位置Y軸座標 watermark_rows: 0, //水印行數 watermark_cols: 0, //水印列數 watermark_x_space: 100, //水印x軸間隔 watermark_y_space: 50, //水印y軸間隔 watermark_font: '微軟雅黑', //水印字體 watermark_color: 'black', //水印字體顏色 watermark_fontsize: '18px', //水印字體大小 watermark_alpha: 0.15, //水印透明度,要求設置在大於等於0.005 watermark_width: 100, //水印寬度 watermark_height: 100, //水印長度 watermark_angle: 15, //水印傾斜度數 watermark_parent_selector: null, //水印插件掛載的父元素選取器,不輸入則默認掛在body上 need_adapt_screen: false, // 是否根據屏幕的分辨率等比變化每一個水印的寬度和字體大小 watermark_width_proportion: 15, // 每一個水印寬度自適應屏幕變化的等比放大或縮小的值 watermark_fontsize_proportion: 95, // 每一個水印字體大小自適應屏幕變化的等比放大或縮小的值 }; let watermark_parent_node = null //水印插件掛載的父元素element,不輸入則默認掛在body上 let setting = arguments[0] || {}; /*採用配置項替換默認值,做用相似jquery.extend*/ if (arguments.length === 1 && typeof arguments[0] === "object") { for (let key in setting) { if (setting[key] && defaultSettings[key] && setting[key] === defaultSettings[key]) continue; /*veronic: resolution of watermark_angle=0 not in force*/ else if (setting[key] || setting[key] === 0) defaultSettings[key] = setting[key]; } } /* 設置水印的容器 */ if (defaultSettings.watermark_parent_selector) { watermark_parent_node = document.querySelector(defaultSettings.watermark_parent_selector) } else { watermark_parent_node = document.body } /*若是元素存在則移除*/ let watermark_element = document.getElementById(defaultSettings.watermark_id); if (watermark_element) { let _parentElement = watermark_element.parentNode; if (_parentElement) { _parentElement.removeChild(watermark_element); } } /*獲取水印的起始位置*/ let page_offsetTop = 0; let page_offsetLeft = 0; page_offsetTop = watermark_parent_node.offsetTop || 0; page_offsetLeft = watermark_parent_node.offsetLeft || 0; page_width = watermark_parent_node.offsetWidth - defaultSettings.watermark_width / 2 || 0; page_height = (Math.max(watermark_parent_node.offsetHeight, watermark_parent_node.scrollHeight) - defaultSettings.watermark_height / 2) || 0; defaultSettings.watermark_x = defaultSettings.watermark_x + page_offsetLeft; defaultSettings.watermark_y = defaultSettings.watermark_y + page_offsetTop; /*建立水印外殼div*/ let otdiv = document.getElementById(defaultSettings.watermark_id); let shadowRoot = null; if (!otdiv) { otdiv = document.createElement('div'); /*建立shadow dom*/ otdiv.id = defaultSettings.watermark_id; otdiv.style.pointerEvents = "none"; /*判斷瀏覽器是否支持attachShadow方法*/ if (typeof otdiv.attachShadow === 'function') { shadowRoot = otdiv.attachShadow({mode: 'open'}); } else if (typeof otdiv.createShadowRoot === 'function') { shadowRoot = otdiv.createShadowRoot(); } else { shadowRoot = otdiv; } watermark_parent_node.appendChild(otdiv) } else if (otdiv.shadowRoot) { shadowRoot = otdiv.shadowRoot; } // shadow內加個容器 let shadowOutDiv = document.createElement('div') shadowOutDiv.id = 'shadowContainer' shadowRoot.appendChild(shadowOutDiv) /*若是將水印列數設置爲0,或水印列數設置過大,超過頁面最大寬度,則從新計算水印列數和水印x軸間隔*/ if (defaultSettings.watermark_cols == 0 || (parseInt(defaultSettings.watermark_x + defaultSettings.watermark_width * defaultSettings.watermark_cols + defaultSettings.watermark_x_space * (defaultSettings.watermark_cols - 1)) > page_width)) { defaultSettings.watermark_cols = parseInt((page_width - defaultSettings.watermark_x + page_offsetLeft) / (defaultSettings.watermark_width + defaultSettings.watermark_x_space)); defaultSettings.watermark_x_space = parseInt((page_width - defaultSettings.watermark_x + page_offsetLeft - defaultSettings.watermark_width * defaultSettings.watermark_cols) / (defaultSettings.watermark_cols - 1)); } /*若是將水印行數設置爲0,或水印行數設置過大,超過頁面最大長度,則從新計算水印行數和水印y軸間隔*/ if (defaultSettings.watermark_rows == 0 || (parseInt(defaultSettings.watermark_y + defaultSettings.watermark_height * defaultSettings.watermark_rows + defaultSettings.watermark_y_space * (defaultSettings.watermark_rows - 1)) > page_height)) { defaultSettings.watermark_rows = parseInt((page_height - defaultSettings.watermark_y + page_offsetTop) / (defaultSettings.watermark_height + defaultSettings.watermark_y_space)); defaultSettings.watermark_y_space = parseInt(((page_height - defaultSettings.watermark_y + page_offsetTop) - defaultSettings.watermark_height * defaultSettings.watermark_rows) / (defaultSettings.watermark_rows - 1)); } let x; let y; for (let i = 0; i < defaultSettings.watermark_rows; i++) { y = defaultSettings.watermark_y + (defaultSettings.watermark_y_space + defaultSettings.watermark_height) * i; for (let j = 0; j < defaultSettings.watermark_cols; j++) { x = defaultSettings.watermark_x + (defaultSettings.watermark_width + defaultSettings.watermark_x_space) * j; let mask_div = document.createElement('div'); let oText = document.createTextNode(defaultSettings.watermark_txt); mask_div.appendChild(oText); /*設置水印相關屬性start*/ mask_div.id = defaultSettings.watermark_prefix + i + j; /*設置水印div傾斜顯示*/ mask_div.style.webkitTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)"; mask_div.style.MozTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)"; mask_div.style.msTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)"; mask_div.style.OTransform = "rotate(-" + defaultSettings.watermark_angle + "deg)"; mask_div.style.transform = "rotate(-" + defaultSettings.watermark_angle + "deg)"; mask_div.style.visibility = ""; mask_div.style.position = "absolute"; /*選不中*/ mask_div.style.left = x + 'px'; mask_div.style.top = y + 'px'; mask_div.style.overflow = "hidden"; mask_div.style.zIndex = "9999999"; mask_div.style.opacity = defaultSettings.watermark_alpha; mask_div.style.fontSize = defaultSettings.watermark_fontsize; mask_div.style.fontFamily = defaultSettings.watermark_font; mask_div.style.color = defaultSettings.watermark_color; mask_div.style.textAlign = "center"; mask_div.style.width = defaultSettings.watermark_width + 'px'; mask_div.style.height = defaultSettings.watermark_height + 'px'; mask_div.style.display = "block"; mask_div.style['-ms-user-select'] = "none"; /*設置水印相關屬性end*/ shadowOutDiv.appendChild(mask_div); } } }; /*初始化水印,添加load和resize事件*/ watermark.init = function (settings) { window.addEventListener('load', function () { loadMark(settings); }); window.addEventListener('resize', function () { loadMark(settings); }); window.addEventListener('DOMContentLoaded', function () { loadMark(settings); }); }; /*手動加載水印*/ watermark.load = function (settings) { loadMark(settings); observerDomReloadMark(settings) }; return watermark; }))
二、優化
可是畢竟水印是有關安全方面的,若是別人打開開發者模式很輕鬆的就水印的內容更改甚至徹底刪除,因此不能就這麼完事了。咱們須要監聽DOM的變化,判斷若是用戶經過開發者工具手動修改了水印的dom,咱們就從新渲染水印,實現水印不能手動進行了修改。web
思路:經過mutationObsever監聽DOM的變化,判斷是不是水印部分的dom 變化,若是是則從新渲染canvas
show me the code:瀏覽器
/* 監聽DOM的變化,防止手動刪除 */ let observerDomReloadMark = (settings) => { // Select the node that will be observed for mutations let observer_node = document.querySelector('#shadowContainer') // Options for the observer (which mutations to observe) let config = { attributes: true, childList: true, subtree: true }; // Callback function to execute when mutations are observed const mutationCallback = (mutationsList) => { // for (let mutation of mutationsList) { // let type = mutation.type; // switch (type) { // case "childList": // console.log("A child node has been added or removed."); // break; // case "attributes": // console.log(`The ${mutation.attributeName} attribute was modified.`); // break; // case "subtree": // console.log(`The subtree was modified.`); // break; // default: // break; // } // } // loadMark(settings) console.log(2222) }; // Create an observer instance linked to the callback function let observer = new MutationObserver(mutationCallback); // Start observing the target node for configured mutations observer.observe(observer_node, config); // Later, you can stop observing // observer.disconnect(); }
這個方法看起來完美,然而現實很殘酷,當咱們再瀏覽器中看效果的時候發現mutationObsever不能監聽shadow的變化,what? 查資料後發現shadow是獨立於dom樹的,至關於被隔離起來的dom。既然如此那就放棄裝逼吧,仍是老老實實用正常的dom實現吧,有多少個水印就生成多個水印的dom,這必要會致使性能問題。好吧,咱們仍是再另尋他法。安全
2、canvas繪圖app
一、基本思路dom
經過canvas把單個水印繪製出來,用toDataURL方法生成圖片的base64編碼,放在水印容器的background-image:url()裏面,背景圖片會自動鋪滿整個容器。思想比第一種方案簡單不少。
show me the code:
var canvas = document.createElement('canvas') canvas.id = 'canvas' canvas.width = defaultSettings.watermark_width // 單個水印的寬度 canvas.height = defaultSettings.watermark_height // 單個水印的高度 var ctx = canvas.getContext('2d') ctx.font = 'normal 12px Microsoft Yahei' // 設置樣式 ctx.fillStyle = 'rgba(112, 113, 114, 0.2)' // 水印字體顏色 ctx.translate(canvas.width/2,canvas.height/2) ctx.rotate((defaultSettings.watermark_angle * Math.PI) / 180) // 水印偏轉角度 ctx.translate(-canvas.width/2,-canvas.height/2) ctx.fillText(defaultSettings.watermark_txt, 30, 20) var src = canvas.toDataURL('image/png') /* 設置水印的容器 */ var watermark_parent_node = null if (defaultSettings.watermark_parent_selector) { watermark_parent_node = document.querySelector(defaultSettings.watermark_parent_selector) } else { watermark_parent_node = document.body } watermark_parent_node.style.pointerEvents = 'none' // 判斷容器是否已經設置了backgroundImage屬性值 if (!watermark_parent_node.style.backgroundImage) { watermark_parent_node.style.backgroundImage = 'URL(' + src + ')' } else { watermark_parent_node.style.backgroundImage = watermark_parent_node.style.backgroundImage + ', URL(' + src + ')' }
二、優化
優化思路和第一種方案同樣,只不過這種方案的好處就是能夠直接監聽到變化。
3、兩種方案的對比
方案 | 優勢 | 缺點 |
shadow DOM | 一、低耦合,shadow dom與原先的DOM樹隔離,不會影響到系統本有的功能。 | 一、shadow DOM不可以被監聽到 二、水印文案及DOM被篡改的成本較低 三、實現邏輯比較複雜 |
canvas繪圖 | 一、實現邏輯比較清晰 二、水印數據生成圖片,用戶想篡改比較難 三、水印被篡改可以被監聽到
|
一、水印圖片是被放在background-image裏面的,若是原先在class裏面設置了background-image屬性的話會被覆蓋掉 |