前言
我們會(huì)看到很多頁(yè)面帶有水印,但是怎么實(shí)現(xiàn)呢?當(dāng)然可以有多種實(shí)現(xiàn)方式,本文主要講解在vue項(xiàng)目中基于DOM或者Cavans實(shí)現(xiàn)水印效果,當(dāng)然還有其他的實(shí)現(xiàn)方式,比如在原圖片的基礎(chǔ)上加上水印生成新的圖片,但是這需要后端處理。因?yàn)橐趘ue項(xiàng)目中使用,所以我使用自定義指令可以直接對(duì)掛載的dom實(shí)現(xiàn)水印效果。
本文實(shí)現(xiàn)水印的項(xiàng)目環(huán)境為:vue + vite + ts
一、vue自定義指令directive講解
前面專(zhuān)門(mén)有一篇講解vue2.x與vue3.x中自定義指令詳解
二、基于DOM的實(shí)現(xiàn)方式
1. 思路整理
-
獲取寬高
(1)獲取綁定元素的實(shí)際寬度clientWidth
(2)獲取綁定元素實(shí)際高度clientHeight
(3)獲取綁定元素的父元素parentElement
-
創(chuàng)建盒子
(1)創(chuàng)建一個(gè)包裹水印圖片的盒子
(2)創(chuàng)建一個(gè)水印圖片的盒子
-
設(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)被綁定元素放到裹水印圖片的盒子里(不然被綁定元素與包裹水印圖片的盒子層級(jí)同級(jí))
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) { const { clientWidth, clientHeight, parentElement } = el; console.log(parentElement, 'parentElement') const waterMark: HTMLElement = document.createElement('div'); const waterBg: HTMLElement = document.createElement('div'); waterMark.className = `water-mark`; waterMark.setAttribute('style', ` display: inline-block;
overflow: hidden;
position: relative;
width: ${clientWidth}px;
height: ${clientHeight}px;`); waterBg.className = `water-mark-bg`; 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; `); waterMark.appendChild(waterBg); 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); }
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');
6. 缺點(diǎn)
-
直接刪除水印元素時(shí),頁(yè)面中的水印直接就被刪除了,當(dāng)然我們可以用
MutationObserver
對(duì)水印元素進(jìn)行監(jiān)聽(tīng),刪除時(shí),我們?cè)倭⒓瓷梢粋€(gè)水印元素就可以了,具體方面在下面講解。
-
如果原始元素本身存在 css 定位等規(guī)則,會(huì)導(dǎo)致整體布局效果出現(xiàn)影響,因?yàn)樯厦鎸?shí)現(xiàn)排除了原始元素沒(méi)有定位,所以實(shí)現(xiàn)方式不是很?chē)?yán)謹(jǐn),本文具體實(shí)現(xiàn)實(shí)現(xiàn)如下:
-
創(chuàng)建一個(gè)水印的容器設(shè)置為
position:relative
-
將原有的節(jié)點(diǎn)放入到這個(gè)容器中
-
同時(shí)創(chuàng)建一個(gè)帶有水印的 dom 設(shè)置為
position:absolute
,實(shí)現(xiàn)這個(gè)水印元素覆蓋到原始元素的上層,以實(shí)現(xiàn)水印的效果。
三、基于Canvas和MutationObserver的實(shí)現(xiàn)方式
1. 思路整理
-
配置水印的具體樣式(大小,旋轉(zhuǎn)角度,文字填充)
-
設(shè)置水?。ㄎ恢茫?
-
監(jiān)聽(tīng)dom變化(防止水印刪除后頁(yè)面不再展示水?。?
2. 生成水印
通過(guò)將圖片繪制在cavans
中,然后通過(guò)cavans
的toDataURL
方法,將圖片轉(zhuǎn)為base64編碼。
const globalCanvas = null; const globalWaterMark = null; const getDataUrl = ( ) => { 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('請(qǐng)勿外傳', 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)聽(tīng)水印
使用MutationObserver
監(jiān)聽(tīng)dom變化,MutationObserver
詳細(xì)用法之前已經(jīng)講過(guò)了,詳細(xì)可見(jiàn)作為前端你還不懂MutationObserver?那Out了
具體監(jiān)聽(tīng)邏輯如下:
-
1.直接刪除dom
(1)先獲取設(shè)置水印的dom
(2)監(jiān)聽(tīng)到被刪除元素的dom
(3)如果他兩相等的話就停止觀察,初始化(設(shè)置水印+啟動(dòng)監(jiān)控)
-
2.刪除style中的屬性
(1)判斷刪除的是否是標(biāo)簽的屬性 (type === “attributes”)
(2)判斷刪除的標(biāo)簽屬性是否是在設(shè)置水印的標(biāo)簽上
(3)判斷修改過(guò)的style和之前的style對(duì)比,不等的話,重新賦值
let style = ` display: block;
overflow: hidden;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-repeat: repeat;
pointer-events: none;`; const setWaterMark = (el: HTMLElement, binding: any) => { const { parentElement } = el; const url = getDataUrl(binding); const waterMark = globalWaterMark || document.createElement("div"); waterMark.className = `water-mark`; style = `${style}background-image: url(${url});`; waterMark.setAttribute("style", style); parentElement.setAttribute("style", "position: relative;"); parentElement.appendChild(waterMark); }; const createObserver = (el: HTMLElement, binding: any) => { console.log(el, 'el') console.log(style, 'style') 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"); if (removedNodes[0] === waterMarkEl) { console.log(removedNodes[0]) observer.disconnect(); 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 = {}) => { setWaterMark(el, binding.value); createObserver(el, binding.value); }; const directives: any = { mounted(el: HTMLElement, binding: any) { 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)簽依然還在,除非刪除水印注冊(cè)的標(biāo)簽才能刪除水印,但是這樣做毫無(wú)意義,因?yàn)檫@樣做內(nèi)容也會(huì)全部刪除掉。
附:文中用到的js基礎(chǔ)知識(shí)
toDataURL用法
toDataURL(type, encoderOptions)
,接收兩個(gè)參數(shù):
-
type:圖片類(lèi)型,比如
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è)計(jì)建立了UI設(shè)計(jì)分享群,每天會(huì)分享國(guó)內(nèi)外的一些優(yōu)秀設(shè)計(jì),如果有興趣的話,可以進(jìn)入一起成長(zhǎng)學(xué)習(xí),請(qǐng)加藍(lán)小助,微信號(hào):ben_lanlan,報(bào)下信息,藍(lán)小助會(huì)請(qǐng)您入群。歡迎您加入噢~~希望得到建議咨詢、商務(wù)合作,也請(qǐng)與我們聯(lián)系01063334945。
分享此文一切功德,皆悉回向給文章原作者及眾讀者.
免責(zé)聲明:藍(lán)藍(lán)設(shè)計(jì)尊重原作者,文章的版權(quán)歸原作者。如涉及版權(quán)問(wèn)題,請(qǐng)及時(shí)與我們?nèi)〉寐?lián)系,我們立即更正或刪除。
藍(lán)藍(lán)設(shè)計(jì)( m.yvirxh.cn )是一家專(zhuān)注而深入的界面設(shè)計(jì)公司,為期望卓越的國(guó)內(nèi)外企業(yè)提供卓越的UI界面設(shè)計(jì)、BS界面設(shè)計(jì) 、 cs界面設(shè)計(jì) 、 ipad界面設(shè)計(jì) 、 包裝設(shè)計(jì) 、 圖標(biāo)定制 、 用戶體驗(yàn) 、交互設(shè)計(jì)、 網(wǎng)站建設(shè) 、平面設(shè)計(jì)服務(wù)、UI設(shè)計(jì)公司、界面設(shè)計(jì)公司、UI設(shè)計(jì)服務(wù)公司、數(shù)據(jù)可視化設(shè)計(jì)公司、UI交互設(shè)計(jì)公司、高端網(wǎng)站設(shè)計(jì)公司、UI咨詢、用戶體驗(yàn)公司、軟件界面設(shè)計(jì)公司