亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

使用Vue實現(xiàn)防篡改的水印

 更新時間:2023年08月27日 08:53:02   作者:子辰Web草廬  
我們在平時上網(wǎng)的時候會看到有些圖片是加水印的,一般水印往往是后端來做的,不過有些站點要保護的知識產(chǎn)權(quán)類型比較多,不光是圖片,可能還有視頻或者文字,所以我們水印的作用,就是給他做一個適當?shù)南拗?本文就給大家介紹一下如何使用Vue實現(xiàn)防篡改的水印

我們在平時上網(wǎng)的時候會看到有些圖片是加水印的:

像這種加水印的操作往往是后端來做的,不過有些站點要保護的知識產(chǎn)權(quán)類型比較多,不光是圖片,可能還有視頻或者文字。

對不同類型的東西去加這個水印,后端操作起來就可能比較麻煩,因為水印這個東西防君子不防小人,他要搞你的話始終能搞你。

所以我們水印的作用,就是給他做一個適當?shù)南拗?,讓他沒有那么輕易的能搞到。

因此現(xiàn)在有些站點開始逐步的讓前端來制作這個水印了。

如果你是用的是 React 來開發(fā)的話就比較簡單了:

這個 Ant Design 這個庫,它本身就有一個組件叫做 Watermark 水印組件,通過這個組件就可以給一個區(qū)域加上一個水印,非常的 so easy 開發(fā)成本極低,無論這個區(qū)域是圖片還是文字或者視頻,都無所謂。

但是如果你使用的是 Vue 來開發(fā)的話很遺憾,無論是 Element UI 還是 Ant Design Vue,都沒有這個 Watermark 組件。

那么就需要我們自己手動的去編寫,其實編寫這個組件也并不復雜,主要是要考慮兩個問題:

  1. 如何來生成水印
  2. 如何來防止篡改

如何生成水印

我們先來看第一步如何生成水印。

基本思路與準備

我們可以有這么一個思路:

比如我們要在上圖的區(qū)域做水印,那么就在區(qū)域里加上一個 div,div 填充滿整個區(qū)域,然后給這個 div 一張水印的背景圖,然后讓背景圖重復就可以了。

這個背景圖我們可以使用 canvas 來畫。

所以基于這么一個思路,我們就可以寫出這么一個代碼結(jié)構(gòu):

我們引入封裝的 Watermark 組件,里邊傳入任何內(nèi)容,可以是文字也可以是視頻,然后就給這個區(qū)域加上水印。

通過 text 傳入水印的文本。

那么我們看看組件里咋寫的:

<template>
  <div class="watermark-container">
    <slot></slot>
    <!-- 我們要做的就是在這里添加一個 div,填充滿整個區(qū)域,設(shè)置水印背景并且重復 -->
  </div>
</template>
<script setup>
import useWatermarkBg from './useWatermarkBg';
// 定義一些基本的屬性( 如果說你想開發(fā)的更加完善,可以加入更多的屬性來適應(yīng)你的要求 )
const props = defineProps({
  text: { // 傳入水印的文本
    type: String,
    required: true,
    default: 'watermark',
  },
  fontSize: { // 字體的大小
    type: Number,
    default: 40,
  },
  gap: { // 水印重復的間隔
    type: Number,
    default: 20,
  },
});
// useWatermarkBg 函數(shù)用來創(chuàng)建一個 canvas 圖片
// 將屬性傳遞進去就返回個創(chuàng)建好的對象
const bg = useWatermarkBg(props);
console.log('bg.value >>> ', bg.value)
</script>

目前組件的代碼還是比較簡單,我們看一下 useWatermarkBg 返回的數(shù)據(jù)是什么:

這里打印了兩個對象,是因為我們有兩個水印區(qū)域,這個對象里有三個屬性:

base64:表示 canvas 生成圖片的 dataurl,到時候就可以用它來做背景 size:表示 canvas 的寬高 styleSize:表示 canvas 的 DPR,如果想要用非常清晰的尺寸的話就用這個,這個值和 window 的devicePixelRatio 有關(guān),如果你不知道的話可以關(guān)注子辰,后期會更新相關(guān)的文章 。

那么我們看看 useWatermarkBg 函數(shù)是怎么寫的,代碼也很簡單:

import { computed } from 'vue';
export default function useWatermarkBg (props) {
  return computed(() => {
    // 創(chuàng)建一個 canvas
    const canvas = document.createElement('canvas');
    const devicePixelRatio = window.devicePixelRatio || 1;
    // 設(shè)置字體大小
    const fontSize = props.fontSize * devicePixelRatio;
    const font = fontSize + 'px serif';
    const ctx = canvas.getContext('2d');
    // 獲取文字寬度
    ctx.font = font;
    const { width } = ctx.measureText(props.text);
    const canvasSize = Math.max(100, width) + props.gap * devicePixelRatio;
    canvas.width = canvasSize;
    canvas.height = canvasSize;
    ctx.translate(canvas.width / 2, canvas.height / 2);
    // 旋轉(zhuǎn) 45 度讓文字變傾斜
    ctx.rotate((Math.PI / 180) * -45);
    ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
    ctx.font = font;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    // 將文字畫出來
    ctx.fillText(props.text, 0, 0);
    return {
      base64: canvas.toDataURL(),
      size: canvasSize,
      styleSize: canvasSize / devicePixelRatio,
    };
  });
}

現(xiàn)在基本的數(shù)據(jù)有了,我們就要生成一個水印的背景的 div,填充在合適的位置。

生成水印填充背景

<template>
  <div class="watermark-container">
    <slot></slot>
    <!-- 我們要做的就是在這里添加一個 div,填充滿整個區(qū)域,設(shè)置水印背景并且重復 -->
  </div>
</template>
<script setup>
import useWatermarkBg from './useWatermarkBg';
const props = defineProps({
	// ...
});
const bg = useWatermarkBg(props);
// 創(chuàng)建一個 div
const div = document.createElement('div');
</script>

我們這里使用 document.createElement 生成一個 div,有同學可能會問,為什么不直接在填充的位置寫一個 div 呢?因為不行,至于為什么不行看到后邊就知道了,在最后進行解釋,現(xiàn)在就使用 dom 來創(chuàng)建這個 div。

現(xiàn)在呢我們給這個 div 設(shè)置一些樣式:

<script setup>
import useWatermarkBg from './useWatermarkBg';
const props = defineProps({
	// ...
});
const bg = useWatermarkBg(props);
const div = document.createElement('div');
// 獲取到解構(gòu)的值
const { base64, styleSize } = bg;
// 背景設(shè)置為 base64 的圖片
div.style.backgroundImage = `url(${base64})`;
// 背景的大小設(shè)置為 styleSize
div.style.backgroundSize = `${styleSize}px ${styleSize}px`;
// 重復方式設(shè)置為 repeat
div.style.backgroundRepeat = 'repeat';
// 設(shè)置子元素與父元素四個方向的間隔(這里設(shè)置為 0 的效果同寬高設(shè)置 100%)
div.style.inset = 0;
// z-index 設(shè)置為 9999 覆蓋上去
div.style.zIndex = 9999;
</script>

樣式我們也只能通過上面的方式來添加,而不能直接寫成 class,具體原因后邊會解釋。

接下來我們要把這個 div 添加到父元素里邊去:

<template>
  <!-- 在父元素上添加 ref -->
  <div class="watermark-container" ref="parentRef">
    <slot></slot>
    <!-- 添加一個div,填充滿整個區(qū)域,設(shè)置水印背景,重復 -->
  </div>
</template>
<script setup>
import { ref, watchEffect } from 'vue';
import useWatermarkBg from './useWatermarkBg';
const props = defineProps({
  // ....
});
// 聲明一個 ref 并添加到父元素上
const parentRef = ref(null);
const bg = useWatermarkBg(props);
// watchEffect 中判斷是否可以獲取到父組件的 ref
watchEffect(() => {
  // 獲取不到,就說明還沒有掛載,先出去
  if (!parentRef.value) {
    return;
  }
  // 獲取到則添加到父元素中
  const { base64, styleSize } = bg;
  const div = document.createElement('div');
  div.style.backgroundImage = `url(${base64})`;
  div.style.backgroundSize = `${styleSize}px ${styleSize}px`;
  div.style.backgroundRepeat = 'repeat';
  div.style.inset = 0;
  div.style.zIndex = 9999;
  // 然后將 div 加到父元素里
  parentRef.value.appendChild(div);
});
</script>

你可以會發(fā)現(xiàn)我們這里使用的是 watchEffect 來判斷是否能獲取到父元素,而不是在 onMounted 里邊,這是因為這一塊會涉及到后邊的防篡改,我們一會就知道了,現(xiàn)在暫且不用管就放在這里。

可以看到 div 已經(jīng)被添加進去了,背景圖以及屬性都是有的,只不過這個 div 不是絕對定位,要填充滿的話就得設(shè)置絕對定位:

<script setup>
// etc...
watchEffect(() => {
  if (!parentRef.value) {
    return;
  }
  const div = document.createElement('div');
  const { base64, styleSize } = bg.value;
  div.style.backgroundImage = `url(${base64})`;
  div.style.backgroundSize = `${styleSize}px ${styleSize}px`;
  div.style.backgroundRepeat = 'repeat';
  div.style.inset = 0;
  div.style.zIndex = 9999;
  // 設(shè)置絕對定位
  div.style.position = 'absolute';
  // 設(shè)置點擊穿漏,防止底部元素失去鼠標事件的交互
  div.style.pointerEvents = 'none';
  parentRef.value.appendChild(div);
});
</script>

你看,現(xiàn)在這個水印就加上了,沒有什么問題,那么第一步加水印就完成了。

接下來我們就要說第二步了,如何防篡改。

如何防篡改

用戶會怎么來篡改我們的水印呢?他有很多辦法,直接在頁面上操作不太可能,他主要的辦法就是進入這個瀏覽器調(diào)試工具,找到我們這個水印的 div 然后刪除:

這樣一刪除就沒了,所以我們僅僅是把這個水印生成出來毫無意義,因為可以輕松的刪除。

那這就要求我們必須要找到某一種方式,能夠監(jiān)控用戶對我們水印元素的操作,比如說刪除。

所以這個防篡改就涉及到兩件事:

  1. 如何監(jiān)控
  2. 重新生成

這就解釋清楚了為什么不直接在父元素里寫 div 的原因,因為直接在父元素里寫的話如果刪除掉的話無法重新生成,但是通過 document 添加的話就可以。

把 div 放在 watchEffect 里邊只要監(jiān)控到用戶動了水印,只要在執(zhí)行一遍 watchEffect 就能重新生成一個新的水印添加進去。

如果說我們不是在 watchEffect 里還是在 onMounted 里就沒辦法那做到重新運行了。

同時也解釋了為什么樣式不能寫在 class 里,因為在 calss 里的話,用戶通過調(diào)試工具更改的話,我們同樣無法監(jiān)控到。

好了,剛才的三個疑問現(xiàn)在都解決了。

如何監(jiān)控

現(xiàn)在的問題就是我們?nèi)绾稳ケO(jiān)控的問題了,我們怎么知道用戶動了水印呢?

那么這里就要說到一個 API 了,叫做 MutationObserve 它可以監(jiān)控一個元素的變化,不僅可以監(jiān)控元素本事,還可以監(jiān)控元素里邊所有的子元素,無論是改動元素的屬性,還是元素的內(nèi)容,這個 API 都可以收到通知。

我們現(xiàn)在就利用這會 API 來實現(xiàn)監(jiān)控,首先我們要搞清楚的是,到底要監(jiān)控誰,我們要監(jiān)控的不是水印的 div,而是整個組件,這樣就可以監(jiān)控到所有的東西了。

所以我們可以這樣寫:

<script setup>
import { ref, watchEffect, onMounted, onUnmounted } from 'vue';
// etc...
let ob;
onMounted(() => {
  // 在 onMounted 里邊創(chuàng)建一個 MutationObserver 來進行監(jiān)控
  // 一旦某個東西有變化就會運行這個回調(diào)函數(shù)
  ob = new MutationObserver((records) => {
    // 并把變化記錄下來傳遞給我們
    console.log('records >>> ', records)
  });
  // 創(chuàng)建好監(jiān)聽器之后,告訴監(jiān)聽器需要監(jiān)聽的元素
  ob.observe(parentRef.value, {
    // 監(jiān)聽的時候需要加一些配置
    childList: true, // 元素內(nèi)容有沒有發(fā)生變化
    attributes: true, // 元素本身的屬性有沒有發(fā)生變化
    subtree: true, // 告訴它監(jiān)控的是整個子樹,就是包含整個子元素
  });
});
// 在組件卸載的時候取消監(jiān)聽
onUnmounted(() => {
  ob && ob.disconnect(); // 取消監(jiān)聽
});
</script>

現(xiàn)在我們就基本設(shè)置好了,看一下效果如何:

在最開始的時候就打印了兩次,因為我們添加了兩次水印的 div,加這個 div 的動作就被監(jiān)聽到了。

返回值是一個數(shù)組,表示我們的操作動作,動作里邊也明確的表示是添加節(jié)點,并且是 div 節(jié)點。

如果我們刪除水印的 div,同樣也觸發(fā)了我們的回調(diào)函數(shù),動作也記錄到了我們刪除了一個 div 的節(jié)點。

通過對動作的了解我們就可以知道如何來監(jiān)控節(jié)點的刪除,獲取到刪除的節(jié)點并且與我們添加的節(jié)點對比,就知道用戶是否刪除了我們的水印節(jié)點,我們就可以這樣來寫:

<script setup>
// 將 div 保存在外部因為要判斷節(jié)點時使用
let div;
watchEffect(() => {
  if (!parentRef.value) {
    return;
  }
  // 判斷之前的節(jié)點是否有內(nèi)容,如果有的話刪除
  if (div) {
    div.remove();
  }
  const { base64, styleSize } = bg.value;
  div = document.createElement('div');
  div.style.backgroundImage = `url(${base64})`;
  div.style.backgroundSize = `${styleSize}px ${styleSize}px`;
  div.style.backgroundRepeat = 'repeat';
  div.style.inset = 0;
  div.style.zIndex = 9999;
  div.style.position = 'absolute';
  div.style.pointerEvents = 'none';
  parentRef.value.appendChild(div);
});
let ob;
onMounted(() => {
  ob = new MutationObserver((records) => {
    // 循環(huán)節(jié)點的動作
    for (const record of records) {
      // 如果有節(jié)點被刪除,循環(huán)一下判斷是否有水印的節(jié)點
      for (const dom of record.removedNodes) {
        if (dom === div) {
          console.log('水印被刪除')
          // ...
          return;
        }
      }
      // 如果有節(jié)點被修改,判斷一下是否是水印的節(jié)點
      if (record.target === div) {
        console.log('屬性被修改')
        // ...
        return;
      }
    }
  });
  ob.observe(parentRef.value, {
    childList: true,
    attributes: true,
    subtree: true,
  });
});
// 在組件卸載的時候取消監(jiān)聽
onUnmounted(() => {
  ob && ob.disconnect(); // 取消監(jiān)聽
  div = null; // 因為 div 是全局變量在寫在的時候值為空
});
</script>

水印刪除后事件就被觸發(fā)了。

屬性被修改時同樣會觸發(fā)事件。

重新生成

那么我們能監(jiān)控到事件了如何重新運行 watchEffect 呢?因為 watchEffect 是收集依賴的,只要依賴變化了它就會重新運行,所以我們可以手動搞一個依賴:

<template>
  <div class="watermark-container" ref="parentRef">
    <slot></slot>
  </div>
</template>
<script setup>
import { onMounted, onUnmounted, ref, watchEffect } from 'vue';
import useWatermarkBg from './useWatermarkBg';
const props = defineProps({
  text: {
    type: String,
    required: true,
    default: 'watermark',
  },
  fontSize: {
    type: Number,
    default: 40,
  },
  gap: {
    type: Number,
    default: 20,
  },
});
const bg = useWatermarkBg(props);
const parentRef = ref(null);
const flag = ref(0); // 聲明一個依賴
let div;
watchEffect(() => {
  flag.value; // 將依賴放在 watchEffect 里
  if (!parentRef.value) {
    return;
  }
  if (div) {
    div.remove();
  }
  const { base64, styleSize } = bg.value;
  div = document.createElement('div');
  div.style.backgroundImage = `url(${base64})`;
  div.style.backgroundSize = `${styleSize}px ${styleSize}px`;
  div.style.backgroundRepeat = 'repeat';
  div.style.zIndex = 9999;
  div.style.position = 'absolute';
  div.style.inset = 0;
  parentRef.value.appendChild(div);
});
let ob;
onMounted(() => {
  ob = new MutationObserver((records) => {
    for (const record of records) {
      for (const dom of record.removedNodes) {
        if (dom === div) {
          flag.value++; // 刪除節(jié)點的時候更新依賴
          return;
        }
      }
      if (record.target === div) {
        flag.value++; // 修改屬性的時候更新依賴
        return;
      }
    }
  });
  ob.observe(parentRef.value, {
    childList: true,
    attributes: true,
    subtree: true,
  });
});
onUnmounted(() => {
  ob && ob.disconnect(); 
  div = null;
});
</script>

這樣就可以完成了,只要監(jiān)控到刪除或者修改屬性,就會重新運行 watchEffect 重新生成一個新的水?。?/p>

總結(jié)

水印是一種保護知識產(chǎn)權(quán)的手段,但是如果只是簡單的生成水印,很容易被用戶篡改或刪除。

所以我們需要使用一些技巧來防止水印被破壞,比如使用 canvas 生成背景圖,使用 document.createElement 添加水印元素,使用 MutationObserver 監(jiān)控元素變化,使用 watchEffect 重新生成水印等。

這樣我們就可以實現(xiàn)一個比較安全的水印組件,提高我們的網(wǎng)站的安全性和可信度。

像 Ant Design 里邊的水印就是這樣做的,沿著這個思路我們就可以一步一步的把這個組件給它完善掉。

以上就是使用Vue實現(xiàn)防篡改的水印的詳細內(nèi)容,更多關(guān)于Vue實現(xiàn)防篡改水印的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue中v-for循環(huán)給標簽屬性賦值的方法

    vue中v-for循環(huán)給標簽屬性賦值的方法

    這篇文章主要介紹了vue中v-for循環(huán)給標簽屬性賦值的方法,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下
    2018-10-10
  • vue二級路由設(shè)置方法

    vue二級路由設(shè)置方法

    下面小編就為大家分享一篇vue二級路由設(shè)置方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-02-02
  • vue實現(xiàn)密碼顯示隱藏功能的思路詳解

    vue實現(xiàn)密碼顯示隱藏功能的思路詳解

    這篇文章主要介紹了vue實現(xiàn)密碼顯示隱藏功能的思路詳解,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-03-03
  • vue實現(xiàn)多欄布局拖拽

    vue實現(xiàn)多欄布局拖拽

    這篇文章主要為大家詳細介紹了vue實現(xiàn)多欄布局拖拽,改變盒子的寬度,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • elementUI table如何給表頭添加氣泡顯示

    elementUI table如何給表頭添加氣泡顯示

    這篇文章主要介紹了elementUI table如何給表頭添加氣泡顯示問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • vue修改對象的屬性值后頁面不重新渲染的實例

    vue修改對象的屬性值后頁面不重新渲染的實例

    今天小編就為大家分享一篇vue修改對象的屬性值后頁面不重新渲染的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-08-08
  • 詳解vue-cli下ESlint 配置說明

    詳解vue-cli下ESlint 配置說明

    這篇文章主要介紹了詳解vue-cli下ESlint 配置說明,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • vue框架render方法如何替換template

    vue框架render方法如何替換template

    這篇文章主要介紹了vue框架render方法如何替換template,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • VUE+elementui組件在table-cell單元格中繪制微型echarts圖

    VUE+elementui組件在table-cell單元格中繪制微型echarts圖

    這篇文章主要介紹了VUE+elementui組件在table-cell單元格中繪制微型echarts圖,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-04-04
  • vue中Promise的使用方法詳情

    vue中Promise的使用方法詳情

    這篇文章主要介紹了vue中Promise的使用方法詳情,Promise可以說是異步編程的一種解決方法,主要是為了解決代碼亂的情景而出現(xiàn)的,下文介紹其具體用法,需要的小伙伴可以參考一下
    2022-03-03

最新評論