最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

前端JavaScript實現(xiàn)添加防刪除水印的方案

 更新時間:2025年10月15日 10:33:48   作者:編程干貨鋪  
最近在做一個 AI 多模態(tài)項目,有個需求是在對話記錄下載預覽彈窗中加水印,還要要防止用戶去掉我們的水印,所以本文給大家介紹了前端添加防刪除水印的技術實現(xiàn),需要的朋友可以參考下

一、背景

最近在做一個 AI 多模態(tài)項目,有個需求是在對話記錄下載預覽彈窗中加水印,還要要防止用戶去掉我們的水印"。 我當時心想,這不就是加個半透明文字嘛,很簡單啊。結(jié)果真動手的時候,才發(fā)現(xiàn)水還挺深的。

二、需求分析

添加水印功能可以拆解為以下 4 個核心維度:

  1. 視覺效果:半透明傾斜文字,鋪滿整個預覽區(qū)域,密度需適中(不能太密也不能太疏)
  2. 用戶體驗:不影響內(nèi)容閱讀,不阻擋用戶點擊、輸入等交互操作
  3. 防刪除能力:用戶通過 F12 開發(fā)者工具刪除水印節(jié)點 / 修改樣式后,水印需自動恢復
  4. 自適應布局:窗口大小改變或容器尺寸變化時,水印布局需實時調(diào)整,避免錯位

2.1 技術方案選型

在前端防刪除水印的實現(xiàn)上,最終敲定 Canvas + MutationObserver 組合方案,核心邏輯與優(yōu)勢如下:

技術模塊核心作用
Canvas動態(tài)繪制水印圖案(支持文字、透明度、旋轉(zhuǎn)角度定制),并轉(zhuǎn)譯為 base64 格式
MutationObserver監(jiān)聽 DOM 樹變化,捕捉水印節(jié)點刪除、樣式篡改等操作,觸發(fā)自動恢復機制

方案核心優(yōu)勢

  • 強安全性:通過「動態(tài)繪制 + 實時監(jiān)控 + 自動恢復」機制,大幅提升水印防篡改能力
  • 高性能:基于單 Canvas 節(jié)點渲染 + 精準監(jiān)聽配置(如 attributeFilter),降低性能損耗
  • 高靈活性:支持水印文字、顏色、透明度、旋轉(zhuǎn)角度、密度等樣式的自由配置,適配多場景

三、核心技術實現(xiàn)與代碼解析

3.1 工具函數(shù)封裝:模塊化設計

將水印邏輯封裝為 createWatermark 工具函數(shù),遵循「可配置、可銷毀、低耦合」原則,支持多場景復用。代碼結(jié)構(gòu)如下:

/**
 * 創(chuàng)建防刪除水印
 * @param {HTMLElement} container - 水印掛載容器
 * @param {WatermarkOptions} options - 水印配置項
 * @returns {() => void} 銷毀函數(shù)(清理監(jiān)聽/定時器/節(jié)點)
 */
export function createWatermark(container, options = {}) {
    // 1. 默認配置(支持外部覆蓋)
    const defaultOptions = {
        text: '智能匯 AI',      // 水印文字
        color: '#000000',      // 文字顏色
        opacity: 0.08,         // 透明度
        fontSize: 18,          // 字體大小(px)
        rotate: -20,           // 旋轉(zhuǎn)角度(°)
        density: 200,          // 單個水印塊尺寸(px),控制密度
    };
    const opts = { ...defaultOptions, ...options };
    
    // 2. 狀態(tài)管理(避免閉包污染,統(tǒng)一維護)
    let watermarkDiv = null;   // 水印DOM節(jié)點
    let observer = null;       // MutationObserver實例
    let resizeTimer = null;    // resize防抖定時器
    
    // 3. 核心方法(下文拆解)
    const createWatermarkElement = () => {};  // 生成水印節(jié)點
    const initObserver = () => {};            // 初始化DOM監(jiān)聽
    const handleResize = () => {};            // 處理窗口resize
    
    // 4. 初始化執(zhí)行
    createWatermarkElement();
    initObserver();
    window.addEventListener('resize', handleResize);
    
    // 5. 返回銷毀函數(shù)(關鍵:避免內(nèi)存泄漏)
    return () => {
        observer?.disconnect();                          // 停止DOM監(jiān)聽
        watermarkDiv?.parentNode?.removeChild(watermarkDiv); // 移除水印節(jié)點
        window.removeEventListener('resize', handleResize);  // 解綁resize事件
        clearTimeout(resizeTimer);                       // 清理定時器
    };
}

設計亮點

  1. 配置模式靈活:采用「默認配置 + 外部覆蓋」模式,支持不同業(yè)務場景的樣式定制;
  2. 資源清理明確:返回銷毀函數(shù),覆蓋監(jiān)聽、定時器、DOM 節(jié)點的清理路徑,解決前端常見的「資源殘留」問題;
  3. 狀態(tài)隔離安全:狀態(tài)變量(watermarkDiv/observer 等)通過閉包封裝,避免全局變量污染。

3.2 Canvas 生成水?。阂曈X層實現(xiàn)

Canvas 是水印視覺效果的核心,通過繪制單個水印塊并轉(zhuǎn)為 base64,再以 background-repeat 平鋪,兼顧性能與視覺效果。

const createWatermarkElement = () => {
    // 1. 創(chuàng)建Canvas實例,設置單個水印塊尺寸
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = opts.density;  // 單個水印寬度 = 密度值
    canvas.height = opts.density; // 單個水印高度 = 密度值

    // 2. 配置文字樣式(關鍵:透明度與旋轉(zhuǎn))
    ctx.font = `${opts.fontSize}px sans-serif`;  // 字體大小
    ctx.fillStyle = opts.color;                  // 文字顏色
    ctx.globalAlpha = opts.opacity;              // 整體透明度(0-1)
    ctx.textAlign = 'center';                    // 文字水平居中
    ctx.textBaseline = 'middle';                 // 文字垂直居中

    // 3. 旋轉(zhuǎn)文字并繪制(弧度轉(zhuǎn)換是關鍵)
    ctx.save(); // 保存當前繪圖狀態(tài)
    // 平移到Canvas中心(避免旋轉(zhuǎn)后文字偏移)
    ctx.translate(canvas.width / 2, canvas.height / 2);
    // 角度轉(zhuǎn)弧度:Math.PI * 角度 / 180
    ctx.rotate((opts.rotate * Math.PI) / 180);
    // 繪制文字(x=0,y=0對應平移后的中心)
    ctx.fillText(opts.text, 0, 0);
    ctx.restore(); // 恢復繪圖狀態(tài)

    // 4. 轉(zhuǎn)為base64,創(chuàng)建水印DOM節(jié)點
    const dataURL = canvas.toDataURL('image/png'); // 轉(zhuǎn)為圖片格式
    watermarkDiv = document.createElement('div');
    watermarkDiv.className = 'watermark-layer';
    watermarkDiv.style.cssText = `
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        pointer-events: none;  // 關鍵:不攔截點擊事件
        z-index: 9999;         // 確保在最上層(避免被遮擋)
        background-image: url(${dataURL});
        background-repeat: repeat; // 平鋪整個容器
    `;

    // 5. 處理容器定位(關鍵:確保水印相對容器定位)
    const containerPosition = window.getComputedStyle(container).position;
    if (containerPosition === 'static') { // static無法作為absolute的包含塊
        container.style.position = 'relative';
    }

    // 6. 掛載水印節(jié)點
    container.appendChild(watermarkDiv);
    return watermarkDiv;
};

技術關鍵點解析

  1. Canvas 尺寸設計:以 density 為單個水印塊的寬高,通過 background-repeat 實現(xiàn)平鋪,避免繪制多個文字導致性能損耗;
  2. 旋轉(zhuǎn)與平移配合:先 translate 到 Canvas 中心再 rotate,防止文字旋轉(zhuǎn)后偏離預期位置;
  3. 交互穿透處理pointer-events: none 讓水印層「穿透」,不攔截容器內(nèi)按鈕、輸入框等元素的點擊 / 輸入事件;
  4. 容器定位修正:若容器為 static(默認定位),自動改為 relative,確保水印相對于容器定位(而非 body)。

3.3 MutationObserver:防篡改實現(xiàn)

用戶通過 F12 刪除水印節(jié)點或修改其 style/class 時,需通過 MutationObserver 監(jiān)聽 DOM 變化并自動恢復,核心是避免「監(jiān)聽 - 修改 - 恢復」的無限循環(huán)。

const initObserver = () => {
    // 1. 創(chuàng)建Observer實例,定義變化處理邏輯
    observer = new MutationObserver((mutations) => {
        let needRestore = false; // 標記是否需要恢復水印
        
        // 2. 遍歷所有DOM變化記錄
        mutations.forEach((mutation) => {
            // 場景1:子節(jié)點被刪除(水印節(jié)點被移除)
            if (mutation.type === 'childList' && mutation.removedNodes.length > 0) {
                mutation.removedNodes.forEach((node) => {
                    // 匹配水印節(jié)點(直接節(jié)點或類名匹配)
                    if (node === watermarkDiv || (node.classList?.contains('watermark-layer'))) {
                        needRestore = true;
                    }
                });
            }
            // 場景2:水印節(jié)點屬性被修改(如style/class被篡改)
            if (mutation.type === 'attributes' && mutation.target === watermarkDiv) {
                // 僅監(jiān)聽關鍵屬性(style/class),減少不必要的判斷
                if (['style', 'class'].includes(mutation.attributeName)) {
                    needRestore = true;
                }
            }
        });

        // 3. 延遲恢復(關鍵:避免無限循環(huán))
        if (needRestore) {
            // 100ms延遲:讓瀏覽器先處理完當前DOM變化,再執(zhí)行恢復
            setTimeout(() => {
                // 雙重判斷:避免重復創(chuàng)建(如多次刪除事件觸發(fā))
                if (!container.querySelector('.watermark-layer')) {
                    createWatermarkElement(); // 重新生成水印
                }
            }, 100);
        }
    });

    // 4. 配置監(jiān)聽選項(最小化監(jiān)聽范圍,提升性能)
    observer.observe(container, {
        childList: true,        // 監(jiān)聽子節(jié)點添加/刪除
        attributes: true,       // 監(jiān)聽節(jié)點屬性變化
        subtree: true,          // 監(jiān)聽后代節(jié)點(避免水印被嵌套刪除)
        attributeFilter: ['style', 'class'], // 僅監(jiān)聽關鍵屬性,減少開銷
    });
};

防無限循環(huán)設計

  1. 延遲 100ms 恢復:若立即恢復,appendChild 會觸發(fā)新的 childList 變化,導致 Observer 再次執(zhí)行,形成無限循環(huán);100ms 延遲讓 DOM 變化事件隊列完成處理后再恢復;
  2. 雙重存在判斷:恢復前先檢查容器內(nèi)是否存在水印節(jié)點,避免重復創(chuàng)建(如用戶連續(xù)刪除多次時,僅恢復一次)。

3.4 窗口自適應:resize 防抖處理

窗口 resize 時,容器尺寸變化會導致水印布局錯位,需重新生成水印。但 resize 事件觸發(fā)頻率極高(每秒數(shù)十次),需通過防抖優(yōu)化性能。

const handleResize = () => {
    // 防抖邏輯:200ms內(nèi)僅執(zhí)行一次重繪
    if (resizeTimer) {
        clearTimeout(resizeTimer); // 清除未執(zhí)行的定時器
    }
    resizeTimer = setTimeout(() => {
        // 先移除舊水印,再創(chuàng)建新水?。ū苊獐B加)
        if (watermarkDiv && watermarkDiv.parentNode) {
            watermarkDiv.parentNode.removeChild(watermarkDiv);
        }
        createWatermarkElement(); // 重新生成適配新尺寸的水印
    }, 200); // 200ms:兼顧響應速度(無明顯卡頓)與性能(減少重繪次數(shù))
};

四、Vue 項目工程化落地

以 Element UI 的 el-dialog(預覽彈窗)為例,需解決「DOM 渲染時序」「組件生命周期」等問題,避免水印節(jié)點掛載失敗。

4.1 核心問題:DOM 渲染時序

el-dialog 打開時存在 300ms 默認動畫,動畫結(jié)束后 DOM 才完全掛載。若在 visible 變化時直接創(chuàng)建水印,會因 previewRef.valuenull 導致掛載失敗。

4.2 最終實現(xiàn)代碼

<template>
  <el-dialog v-model="visible" title="對話記錄預覽">
    <div ref="previewRef" class="preview-container">
      </div>
  </el-dialog>
</template>

<script setup>
import { ref, watch, onUnmounted, nextTick } from 'vue';
import { createWatermark } from '@/utils/watermarkUtil';

// 1. 狀態(tài)管理
const visible = ref(false);       // 彈窗顯示狀態(tài)
const previewRef = ref(null);     // 水印掛載容器ref
let destroyWatermark = null;      // 水印銷毀函數(shù)(全局維護)

// 2. 水印初始化(核心:處理DOM時序)
const initWatermark = async () => {
  // 第一步:清理舊水印(避免重復創(chuàng)建)
  if (destroyWatermark) {
    destroyWatermark();
    destroyWatermark = null;
  }
  // 第二步:等待Vue DOM更新(nextTick:確保ref已關聯(lián)DOM)
  await nextTick();
  // 第三步:等待el-dialog動畫結(jié)束(300ms:匹配組件默認動畫時長)
  setTimeout(() => {
    // 雙重判斷:確保容器DOM存在
    if (previewRef.value) {
      destroyWatermark = createWatermark(previewRef.value, {
        text: '智能匯 AI',
        opacity: 0.08,
        density: 200,
      });
    }
  }, 300);
};

// 3. 監(jiān)聽彈窗狀態(tài)變化
watch(visible, (isVisible) => {
  if (isVisible) {
    initWatermark(); // 彈窗打開:初始化水印
  } else {
    // 彈窗關閉:清理水?。ㄡ尫刨Y源)
    destroyWatermark?.();
    destroyWatermark = null;
  }
});

// 4. 組件卸載:強制清理(避免內(nèi)存泄漏)
onUnmounted(() => {
  destroyWatermark?.();
  destroyWatermark = null;
});
</script>

時序控制關鍵點

  1. nextTick 確保 DOM 關聯(lián):等待 Vue 響應式更新完成,確保 previewRef.value 已指向真實 DOM;
  2. 300ms 延遲匹配動畫:匹配 el-dialog 的 transition 動畫時長(默認 300ms),動畫結(jié)束后容器尺寸才穩(wěn)定;
  3. 多時機資源清理:彈窗關閉、組件卸載時均調(diào)用銷毀函數(shù),覆蓋所有資源釋放場景,避免內(nèi)存泄漏。

五、總結(jié)與技術思考

  1. 需求拆解的重要性:從「加個水印」到「防刪除、自適應、無感知」,需將模糊需求轉(zhuǎn)化為可量化的技術指標(如透明度≤0.1、恢復延遲≤100ms),避免開發(fā)偏差;
  2. 時序控制是前端難點:Vue 的 nextTick、組件庫的動畫生命周期、DOM 事件循環(huán),需精準把控各環(huán)節(jié)的執(zhí)行順序,否則易出現(xiàn)「DOM 未掛載」「節(jié)點重復創(chuàng)建」等問題;
  3. 資源清理是工程化基礎MutationObserver、resize 事件、定時器等必須在合適時機銷毀,尤其是在 Vue/React 等框架中,需結(jié)合組件生命周期完善清理邏輯;
  4. 封裝復用的設計思維:工具函數(shù)需兼顧「可配置性」與「低耦合性」,通過返回銷毀函數(shù)、抽離默認配置等方式,降低業(yè)務接入成本,適配多場景復用。

本方案已在 AI 多模態(tài)項目中穩(wěn)定運行,支持對話記錄、報告預覽等多個彈窗場景,可直接復用或根據(jù)業(yè)務需求調(diào)整配置項。

以上就是前端JavaScript實現(xiàn)添加防刪除水印的方案的詳細內(nèi)容,更多關于JavaScript添加防刪除水印的資料請關注腳本之家其它相關文章!

相關文章

  • amd、cmd、esmodule、commonjs區(qū)別詳解

    amd、cmd、esmodule、commonjs區(qū)別詳解

    本文主要介紹了amd、cmd、esmodule、commonjs區(qū)別詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-04-04
  • js事件觸發(fā)操作實例分析

    js事件觸發(fā)操作實例分析

    這篇文章主要介紹了js事件觸發(fā)操作,結(jié)合實例形式分析了javascript事件觸發(fā)機制原理、使用方法及相關操作注意事項,需要的朋友可以參考下
    2019-06-06
  • JS代碼判斷集錦大全

    JS代碼判斷集錦大全

    本文通過實例代碼給大家介紹了js代碼判斷的方法,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友參考下吧
    2007-12-12
  • Asp.Net alert彈出提示信息的幾種方法總結(jié)

    Asp.Net alert彈出提示信息的幾種方法總結(jié)

    本篇文章主要是對Asp.Net alert彈出提示信息的幾種方法進行了總結(jié)介紹,需要的朋友可以過來參考下,希望對大家有所幫助
    2014-01-01
  • JS中數(shù)組重排序方法

    JS中數(shù)組重排序方法

    在數(shù)組中有兩個可以用來直接排序的方法,分別是reverse()和sort()。下面通過本文給大家詳細介紹,對js 數(shù)組重排序相關知識感興趣的朋友一起看看吧
    2016-11-11
  • js報錯:Maximum?call?stack?size?exceeded的解決方法

    js報錯:Maximum?call?stack?size?exceeded的解決方法

    這篇文章主要給大家介紹了關于js報錯Maximum?call?stack?size?exceeded的解決方法,文中通過實例代碼將解決的方法介紹的非常詳細,需要的朋友可以參考下
    2023-02-02
  • js綁定事件this指向發(fā)生改變的問題解決方法

    js綁定事件this指向發(fā)生改變的問題解決方法

    js綁定事件this指向發(fā)生改變的問題將在本文進行詳細探討下,感興趣的朋友可以參考下哈,希望對你有所幫助
    2013-04-04
  • vue(javaScript)操作字符串的一些常用方法總結(jié)

    vue(javaScript)操作字符串的一些常用方法總結(jié)

    在平時前端開發(fā)中,我們不難發(fā)現(xiàn)經(jīng)常會用到字符串操作,這篇文章主要給大家介紹了關于vue(javaScript)操作字符串的一些常用方法的相關資料,需要的朋友可以參考下
    2024-01-01
  • JavaScript實現(xiàn)LRU緩存淘汰算法的詳細步驟

    JavaScript實現(xiàn)LRU緩存淘汰算法的詳細步驟

    這篇文章主要介紹了JavaScript實現(xiàn)LRU緩存淘汰算法,下面是用 JavaScript 實現(xiàn) LRU(Least RecentlyUsed,最近最少使用)緩存淘汰算法的代碼,并附上詳細的步驟解釋,需要的朋友可以參考下
    2024-12-12
  • js實現(xiàn)樓層導航功能

    js實現(xiàn)樓層導航功能

    本文主要介紹了js實現(xiàn)樓層導航功能的示例,具有很好的參考價值,下面跟著小編一起來看下吧
    2017-02-02

最新評論

邻水| 金华市| 华宁县| 民丰县| 隆化县| 体育| 望都县| 扶沟县| 天峨县| 潜山县| 延川县| 广汉市| 定日县| 景洪市| 天等县| 平乡县| 克什克腾旗| 武冈市| 万载县| 金华市| 太白县| 常德市| 抚顺县| 苏尼特右旗| 香格里拉县| 谢通门县| 金秀| 涟水县| 安顺市| 昌乐县| 丰城市| 乐都县| 德化县| 天全县| 平陆县| 许昌县| 蓬溪县| 读书| 灯塔市| 胶南市| 云阳县|