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

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

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

一、背景

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

二、需求分析

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

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

2.1 技術(shù)方案選型

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

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

方案核心優(yōu)勢

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

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

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

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

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

設(shè)計(jì)亮點(diǎn)

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

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

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

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

    // 2. 配置文字樣式(關(guān)鍵:透明度與旋轉(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)換是關(guān)鍵)
    ctx.save(); // 保存當(dāng)前繪圖狀態(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對應(yīng)平移后的中心)
    ctx.fillText(opts.text, 0, 0);
    ctx.restore(); // 恢復(fù)繪圖狀態(tài)

    // 4. 轉(zhuǎn)為base64,創(chuàng)建水印DOM節(jié)點(diǎn)
    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;  // 關(guān)鍵:不攔截點(diǎn)擊事件
        z-index: 9999;         // 確保在最上層(避免被遮擋)
        background-image: url(${dataURL});
        background-repeat: repeat; // 平鋪整個(gè)容器
    `;

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

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

技術(shù)關(guān)鍵點(diǎn)解析

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

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

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

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

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

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

防無限循環(huán)設(shè)計(jì)

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

3.4 窗口自適應(yīng):resize 防抖處理

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

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

四、Vue 項(xiàng)目工程化落地

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

4.1 核心問題:DOM 渲染時(shí)序

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

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

<template>
  <el-dialog v-model="visible" title="對話記錄預(yù)覽">
    <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ù)(全局維護(hù))

// 2. 水印初始化(核心:處理DOM時(shí)序)
const initWatermark = async () => {
  // 第一步:清理舊水?。ū苊庵貜?fù)創(chuàng)建)
  if (destroyWatermark) {
    destroyWatermark();
    destroyWatermark = null;
  }
  // 第二步:等待Vue DOM更新(nextTick:確保ref已關(guān)聯(lián)DOM)
  await nextTick();
  // 第三步:等待el-dialog動(dòng)畫結(jié)束(300ms:匹配組件默認(rèn)動(dòng)畫時(shí)長)
  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 {
    // 彈窗關(guān)閉:清理水?。ㄡ尫刨Y源)
    destroyWatermark?.();
    destroyWatermark = null;
  }
});

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

時(shí)序控制關(guān)鍵點(diǎn)

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

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

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

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

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

相關(guān)文章

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

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

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

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

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

    JS代碼判斷集錦大全

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

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

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

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

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

    js報(bào)錯(cuò):Maximum?call?stack?size?exceeded的解決方法

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

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

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

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

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

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

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

    js實(shí)現(xiàn)樓層導(dǎo)航功能

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

最新評論

越西县| 太原市| 韩城市| 东阳市| 岑巩县| 贡嘎县| 综艺| 岑溪市| 星子县| 翼城县| 潮安县| 专栏| 兴业县| 江源县| 庆元县| 宕昌县| 越西县| 贵定县| 铜山县| 东台市| 宜春市| 扶余县| 镇远县| 通道| 安宁市| 辽宁省| 江津市| 麟游县| 宁河县| 井陉县| 曲松县| 法库县| 保山市| 松江区| 阿拉尔市| 治县。| 乐清市| 青岛市| 鄂温| 诸城市| 绥江县|