前端基于DOM或者Canvas實(shí)現(xiàn)頁面水印

2023-2-13    前端達(dá)人

前言

我們會看到很多頁面帶有水印,但是怎么實(shí)現(xiàn)呢?當(dāng)然可以有多種實(shí)現(xiàn)方式,本文主要講解在vue項目中基于DOM或者Cavans實(shí)現(xiàn)水印效果,當(dāng)然還有其他的實(shí)現(xiàn)方式,比如在原圖片的基礎(chǔ)上加上水印生成新的圖片,但是這需要后端處理。因?yàn)橐趘ue項目中使用,所以我使用自定義指令可以直接對掛載的dom實(shí)現(xiàn)水印效果。

本文實(shí)現(xiàn)水印的項目環(huán)境為:vue + vite + ts

一、vue自定義指令directive講解

前面專門有一篇講解vue2.x與vue3.x中自定義指令詳解

二、基于DOM的實(shí)現(xiàn)方式

1. 思路整理

  • 獲取寬高
    (1)獲取綁定元素的實(shí)際寬度clientWidth
    (2)獲取綁定元素實(shí)際高度clientHeight
    (3)獲取綁定元素的父元素parentElement
  • 創(chuàng)建盒子
    (1)創(chuàng)建一個包裹水印圖片的盒子
    (2)創(chuàng)建一個水印圖片的盒子
  • 設(shè)置盒子樣式
    (1)包裹水印盒子寬高為綁定元素的寬高,即clientWidth、clientHeight
    (2)水印盒子設(shè)置背景圖、旋轉(zhuǎn)度、寬高、點(diǎn)擊穿透
  • 設(shè)置創(chuàng)建的元素的位置
    (1)水印盒子放到包裹水印圖片的盒子里 (包裹水印圖片的盒子包裹水?。?br style="box-sizing:border-box;outline:0px;user-select:text !important;overflow-wrap:break-word;" /> (2)包裹水印圖片的盒子放到被綁定元素之前
    (3)被綁定元素放到裹水印圖片的盒子里(不然被綁定元素與包裹水印圖片的盒子層級同級)

2.新建index.vue

將水印的指令放到標(biāo)簽上,設(shè)置標(biāo)簽的寬高。水印可以放大div標(biāo)簽上,也可以是img標(biāo)簽上。注意:img才有onload方法,div標(biāo)簽么有。

<script setup lang="ts"> import { ref } from "vue"; </script> <template> <div class="index-content" > <div class="watermaker" v-watermark ></div> <!-- <img v-watermark style="width:400px;height:400px" src="../assets/vue.svg" alt=""> --> </div> </template> <style scoped> .watermaker { width: 400px; height: 400px; } .index-content{ width: 100%; height: 100%; } </style> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

3. 新建directives文件

directives文件下創(chuàng)建waterMark.ts 文件,具體內(nèi)容實(shí)現(xiàn)如下:

import waterImg from "@/assets/vue.svg" const directives: any = { mounted(el: HTMLElement) { //如果el元素是img,則可以用el.onload將下面包裹 const { clientWidth, clientHeight, parentElement } = el; console.log(parentElement, 'parentElement') const waterMark: HTMLElement = document.createElement('div'); const waterBg: HTMLElement = document.createElement('div'); //設(shè)置waterMark的class和style waterMark.className = `water-mark`; waterMark.setAttribute('style', ` display: inline-block;
            overflow: hidden;
            position: relative;
            width: ${clientWidth}px; 
            height: ${clientHeight}px;`); // 創(chuàng)建waterBg的class和style waterBg.className = `water-mark-bg`;// 方便自定義展示結(jié)果 waterBg.setAttribute('style', ` position: absolute;
            pointer-events: none;`在這里插入代碼片` transform: rotate(45deg);
            width: 100%;
            height: 100%;
            opacity: 0.2;
            background-image: url(${waterImg}); 
            background-repeat: repeat; `); // 水印元素waterBg放到waterMark元素中 waterMark.appendChild(waterBg); //waterMark插入到el之前,即插入到綁定元素之前 parentElement?.insertBefore(waterMark, el); // 綁定元素移入到包裹水印的盒子 waterMark.appendChild(el); } } export default { name: 'watermark', directives } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

4. 在directives文件下創(chuàng)建 index.ts文件

import type { App } from 'vue' import watermark from './waterMark' export default function installDirective(app: App) { app.directive(watermark.name, watermark.directives); } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

5. 在main.ts中全局引入

import { createApp } from 'vue' import App from './App.vue' import directives from './directives' const app = createApp(App); app.use(directives); app.mount('#app'); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

6. 缺點(diǎn)

  • 直接刪除水印元素時,頁面中的水印直接就被刪除了,當(dāng)然我們可以用MutationObserver對水印元素進(jìn)行監(jiān)聽,刪除時,我們再立即生成一個水印元素就可以了,具體方面在下面講解。
  • 如果原始元素本身存在 css 定位等規(guī)則,會導(dǎo)致整體布局效果出現(xiàn)影響,因?yàn)樯厦鎸?shí)現(xiàn)排除了原始元素沒有定位,所以實(shí)現(xiàn)方式不是很嚴(yán)謹(jǐn),本文具體實(shí)現(xiàn)實(shí)現(xiàn)如下:
    • 創(chuàng)建一個水印的容器設(shè)置為 position:relative
    • 將原有的節(jié)點(diǎn)放入到這個容器中
    • 同時創(chuàng)建一個帶有水印的 dom 設(shè)置為position:absolute ,實(shí)現(xiàn)這個水印元素覆蓋到原始元素的上層,以實(shí)現(xiàn)水印的效果。

三、基于Canvas和MutationObserver的實(shí)現(xiàn)方式

1. 思路整理

  • 配置水印的具體樣式(大小,旋轉(zhuǎn)角度,文字填充)
  • 設(shè)置水印(位置)
  • 監(jiān)聽dom變化(防止水印刪除后頁面不再展示水?。?

2. 生成水印

通過將圖片繪制在cavans中,然后通過cavanstoDataURL方法,將圖片轉(zhuǎn)為base64編碼。

// 全局保存 canvas 和 div ,避免重復(fù)創(chuàng)建(單例模式) const globalCanvas = null; const globalWaterMark = null; // 獲取 toDataURL 的結(jié)果 const getDataUrl = ( // font = "16px normal", // fillStyle = "rgba(180, 180, 180, 0.3)", // textAlign, // textBaseline, // text = "請勿外傳", ) => { const rotate = -10; const canvas = globalCanvas || document.createElement("canvas"); const ctx = canvas.getContext("2d"); // 獲取畫布上下文 ctx.rotate((rotate * Math.PI) / 180); ctx.font = "16px normal"; ctx.fillStyle = "rgba(180, 180, 180, 0.3)"; ctx.textAlign = "left"; ctx.textBaseline = "middle"; ctx.fillText('請勿外傳', canvas.width / 3, canvas.height / 2); return canvas.toDataURL("image/png"); }; 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3. 使用MutationObserver監(jiān)聽水印

使用MutationObserver監(jiān)聽dom變化,MutationObserver詳細(xì)用法之前已經(jīng)講過了,詳細(xì)可見作為前端你還不懂MutationObserver?那Out了
具體監(jiān)聽邏輯如下:

  • 1.直接刪除dom
    (1)先獲取設(shè)置水印的dom
    (2)監(jiān)聽到被刪除元素的dom
    (3)如果他兩相等的話就停止觀察,初始化(設(shè)置水印+啟動監(jiān)控)
  • 2.刪除style中的屬性
    (1)判斷刪除的是否是標(biāo)簽的屬性 (type === “attributes”)
    (2)判斷刪除的標(biāo)簽屬性是否是在設(shè)置水印的標(biāo)簽上
    (3)判斷修改過的style和之前的style對比,不等的話,重新賦值
// watermark 樣式 let style = ` display: block;
overflow: hidden;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-repeat: repeat;
pointer-events: none;`; //設(shè)置水印 const setWaterMark = (el: HTMLElement, binding: any) => { const { parentElement } = el; // 獲取對應(yīng)的 canvas 畫布相關(guān)的 base64 url const url = getDataUrl(binding); // 創(chuàng)建 waterMark 父元素 const waterMark = globalWaterMark || document.createElement("div"); waterMark.className = `water-mark`; // 方便自定義展示結(jié)果 style = `${style}background-image: url(${url});`; waterMark.setAttribute("style", style); // 將對應(yīng)圖片的父容器作為定位元素 parentElement.setAttribute("style", "position: relative;"); // 將圖片元素移動到 waterMark 中 parentElement.appendChild(waterMark); }; // 監(jiān)聽 DOM 變化 const createObserver = (el: HTMLElement, binding: any) => { console.log(el, 'el') console.log(style, 'style') // console.log(el.parentElement.querySelector('.water-mark'),'el.parentElement') const waterMarkEl = el.parentElement.querySelector(".water-mark"); const observer = new MutationObserver((mutationsList) => { console.log(mutationsList, 'mutationsList') if (mutationsList.length) { const { removedNodes, type, target } = mutationsList[0]; const currStyle = waterMarkEl.getAttribute("style"); // console.log(currStyle, 'currStyle') // 證明被刪除了 //   (1)直接刪除dom //   1.先獲取設(shè)置水印的dom //   2.監(jiān)聽到被刪除元素的dom //   如果他兩相等的話就停止觀察,初始化(設(shè)置水印+啟動監(jiān)控) //   (2) 刪除style中的屬性 //  1 判斷刪除的是否是標(biāo)簽的屬性 (type === "attributes") //  2.判斷刪除的標(biāo)簽屬性是否是在設(shè)置水印的標(biāo)簽上 //  3.判斷修改過的style和之前的style對比,不等的話,重新賦值 if (removedNodes[0] === waterMarkEl) { console.log(removedNodes[0]) // 停止觀察。調(diào)用該方法后,DOM 再發(fā)生變動,也不會觸發(fā)觀察器。 observer.disconnect(); //初始化(設(shè)置水印,啟動監(jiān)控) init(el, binding); } else if ( type === "attributes" && target === waterMarkEl && currStyle !== style ) { console.log(currStyle, 'currStyle') console.log(style, 'style') waterMarkEl.setAttribute("style", style); } } }); observer.observe(el.parentElement, { childList: true, attributes: true, subtree: true, }); }; // 初始化 const init = (el: HTMLElement, binding: any = {}) => { // 設(shè)置水印 setWaterMark(el, binding.value); // 啟動監(jiān)控 createObserver(el, binding.value); }; const directives: any = { mounted(el: HTMLElement, binding: any) { //注意img有onload的方法,如果自定義指令注冊在html標(biāo)簽的話,只需要init(el, binding.value) el.onload = init.bind(null, el, binding.value); }, }; 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87

四、成果展示

刪除水印標(biāo)簽依然還在,除非刪除水印注冊的標(biāo)簽才能刪除水印,但是這樣做毫無意義,因?yàn)檫@樣做內(nèi)容也會全部刪除掉。

在這里插入圖片描述

附:文中用到的js基礎(chǔ)知識

toDataURL用法

toDataURL(type, encoderOptions),接收兩個參數(shù):

  • type:圖片類型,比如image/png、image/jpeg、image/webp等等,默認(rèn)為image/png格式
  • encoderOptions:圖片質(zhì)量的取值范圍(0-1),默認(rèn)值為0.92,當(dāng)超出界限按默認(rèn)值0.92






藍(lán)藍(lán)設(shè)計建立了UI設(shè)計分享群,每天會分享國內(nèi)外的一些優(yōu)秀設(shè)計,如果有興趣的話,可以進(jìn)入一起成長學(xué)習(xí),請加藍(lán)小助,微信號:ben_lanlan,報下信息,藍(lán)小助會請您入群。歡迎您加入噢~~希望得到建議咨詢、商務(wù)合作,也請與我們聯(lián)系01063334945。


分享此文一切功德,皆悉回向給文章原作者及眾讀者.
免責(zé)聲明:藍(lán)藍(lán)設(shè)計尊重原作者,文章的版權(quán)歸原作者。如涉及版權(quán)問題,請及時與我們?nèi)〉寐?lián)系,我們立即更正或刪除。


藍(lán)藍(lán)設(shè)計www.bouu.cn )是一家專注而深入的界面設(shè)計公司,為期望卓越的國內(nèi)外企業(yè)提供卓越的UI界面設(shè)計、BS界面設(shè)計 、 cs界面設(shè)計 、 ipad界面設(shè)計 、 包裝設(shè)計 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計、 網(wǎng)站建設(shè) 、平面設(shè)計服務(wù)、UI設(shè)計公司、界面設(shè)計公司、UI設(shè)計服務(wù)公司、數(shù)據(jù)可視化設(shè)計公司、UI交互設(shè)計公司、高端網(wǎng)站設(shè)計公司、UI咨詢、用戶體驗(yàn)公司、軟件界面設(shè)計公司

分享本文至:

日歷

鏈接

個人資料

存檔