目錄
分析問題
解決問題
總結
首頁 web前端 js教程 你知道前端是如何實現浮水印的嗎

你知道前端是如何實現浮水印的嗎

Jul 30, 2021 pm 04:57 PM
node.js 前端

在日常工作中,經常會遇到很多敏感的數據,為防止數據的洩露,我們要在數據上做一些」包裝「。目的就是讓那些有心洩露資料的」不法分子「迫於嚴重的」輿論壓力「而放棄不法行為,使之」犯罪未遂“,達到不戰而屈人之兵的效果。

在安全部門工作的我們,資料安全的觀念早已深入骨髓,每個文字,每張圖片,都要留心是否有洩露的風險,怎麼防止資料洩露,是我們一直在思考的問題。例如圖片的浮水印,就是我們工作過程中常涉及的問題。因為本身工作內容就是審核平台的開發,經常有一些風險圖片會在審核平台出現,考慮到審核人員的安全意識參差不齊,所以為防止不安全的事情發生,圖片增加水印的工作是必須要做的。

分析問題

首先,考慮到業務場景,現階段的問題只是在審核過程中擔心資料的洩露,我們暫時只考慮顯式水印,既在圖片上增加一些可以區別你個人身分的文字或其他資料。這樣就可以做到根據洩漏的資料可以追蹤到個人,當然,未雨綢繆,防患於未然的警示功能才是它最主要。

解決問題

實作方式

#水印的實作方式有很多,根據實作功能的人員分工可以分為前端浮水印和後端浮水印,前端浮水印的優點可以總結為三點,第一,可以不佔用伺服器資源,完全依賴客戶端的運算能力,減少服務端壓力。第二,速度快,無論哪一種前端的實作方式,效能都是優於後端的。第三,實作方式簡單。後端實現水印的最大優勢也可以總結為三點,就是安全,安全,安全。知乎,微博都是採用後端實現的水印方案。但綜合考慮,我們還是採用前端實現浮水印的方案。下面也會簡單介紹下 nodejs 怎麼實作後端圖片浮水印。

node實作

提供三個 npm 包,本部分不是我們文章的重點,只提供簡單的 demo。

1,gm https://github.com/aheckmann/gm 6.4k star

const fs = require('fs');
const gm = require('gm');


gm('/path/to/my/img.jpg')
.drawText(30, 20, "GMagick!")
.write("/path/to/drawing.png", function (err) {
  if (!err) console.log('done');
});
登入後複製

需要安裝GraphicsMagick 或ImageMagick;

2,node-images:https: //github.com/zhangyuanwei/node-images

const wrap = document.querySelector('#ReactApp');
const { clientWidth, clientHeight } = wrap;
const waterHeight = 120;
const waterWidth = 180;
// 计算个数
const [columns, rows] = [~~(clientWidth / waterWidth), ~~(clientHeight / waterHeight)]
for (let i = 0; i < columns; i++) {
    for (let j = 0; j <= rows; j++) {
        const waterDom = document.createElement(&#39;div&#39;);
        // 动态设置偏移值
        waterDom.setAttribute(&#39;style&#39;, `
            width: ${waterWidth}px; 
            height: ${waterHeight}px; 
            left: ${waterWidth + (i - 1) * waterWidth + 10}px;
            top: ${waterHeight + (j - 1) * waterHeight + 10}px;
            color: #000;
            position: absolute`
        );
        waterDom.innerText = &#39;测试水印&#39;;
        wrap.appendChild(waterDom);
    }
}
登入後複製

不需要安裝其他工具,輕量級,zhangyuanwei 國人開發,中文文件;

#3,jimp:https://github .com/oliver-moran/jimp

可搭配gifwrap 實作gif 浮水印;

##前端實作

1,背景圖實作全螢幕浮水印

可以到阿里內外個人資訊頁面查看效果

優點:圖片是後端生成,安全;

缺點:需要發起http 請求,取得圖片資訊;

效果展示:由於是內部系統,不方便展示效果。

2,dom 實現全圖浮水印和圖片浮水印

在圖片的onload 事件裡獲取圖片寬高,根據圖片大小生成水印區域,遮擋在圖片上層,dom 內容為水印的文案或其他訊息,實現方式比較簡單。

const wrap = document.querySelector(&#39;#ReactApp&#39;);
const { clientWidth, clientHeight } = wrap;
const waterHeight = 120;
const waterWidth = 180;
// 计算个数
const [columns, rows] = [~~(clientWidth / waterWidth), ~~(clientHeight / waterHeight)]
for (let i = 0; i < columns; i++) {
    for (let j = 0; j <= rows; j++) {
        const waterDom = document.createElement(&#39;p&#39;);
        // 动态设置偏移值
        waterDom.setAttribute(&#39;style&#39;, `
            width: ${waterWidth}px; 
            height: ${waterHeight}px; 
            left: ${waterWidth + (i - 1) * waterWidth + 10}px;
            top: ${waterHeight + (j - 1) * waterHeight + 10}px;
            color: #000;
            position: absolute`
        );
        waterDom.innerText = &#39;测试水印&#39;;
        wrap.appendChild(waterDom);
    }
}
登入後複製

優點:簡單易實作;

缺點:圖片過大或過多會有效能影響;

3,canvas 實作方式(第一版實作方案)

方法一:直接在圖片上操作

廢話不多說,直接上程式碼

useEffect(() => {
      // gif 图不支持
    if (src && src.includes(&#39;.gif&#39;)) {
      setShowImg(true);
    }
    image.onload = function () {
      try {
        // 太小的图不加载水印
        if (image.width < 10) {
          setIsDataError(true);
          props.setIsDataError && props.setIsDataError(true);
          return;
        }
        const canvas = canvasRef.current;
        canvas.width = image.width;
        canvas.height = image.height;
        // 设置水印
        const font = `${Math.min(Math.max(Math.floor(innerCanvas.width / 14), 14), 48)}px` || fontSize;
        innerContext.font = `${font} ${fontFamily}`;
        innerContext.textBaseline = &#39;hanging&#39;;
        innerContext.rotate(rotate * Math.PI / 180);
        innerContext.lineWidth = lineWidth;
        innerContext.strokeStyle = strokeStyle;
        innerContext.strokeText(text, 0, innerCanvas.height / 4 * 3);
        innerContext.fillStyle = fillStyle;
        innerContext.fillText(text, 0, innerCanvas.height / 4 * 3);
        const context = canvas.getContext(&#39;2d&#39;);
        context.drawImage(this, 0, 0);
        context.rect(0, 0, image.width || 200, image.height || 200);
           // 设置水印浮层
        const pattern = context.createPattern(innerCanvas, &#39;repeat&#39;);
        context.fillStyle = pattern;
        context.fill();
      } catch (err) {
        console.info(err);
        setShowImg(true);
      }
    };
    image.onerror = function () {
      setShowImg(true);
    };
  }, [src]);
登入後複製

優點:純前端實作方式,右鍵複製的圖片也是有浮水印的;

缺點:不支援gif,圖片必須支援跨域;

效果展示:下文給出。

方法二:canvas 產生水印url 賦值給css background 屬性

export const getBase64Background = (props) => {
  const { nick, empId } = GlobalConfig.userInfo;
  const {
    rotate = -20,
    height = 75,
    width = 85,
    text = `${nick}-${empId}`,
    fontSize = &#39;14px&#39;,
    lineWidth = 2,
    fontFamily = &#39;microsoft yahei&#39;,
    strokeStyle = &#39;rgba(255, 255, 255, .15)&#39;,
    fillStyle = &#39;rgba(0, 0, 0, 0.15)&#39;,
    position = { x: 30, y: 30 },
  } = props;
  const image = new Image();
  image.crossOrigin = &#39;Anonymous&#39;;
  const canvas = document.createElement(&#39;canvas&#39;);
  const context = canvas.getContext(&#39;2d&#39;);
  canvas.width = width;
  canvas.height = height;
  context.font = `${fontSize} ${fontFamily}`;
  context.lineWidth = lineWidth;
  context.rotate(rotate * Math.PI / 180);
  context.strokeStyle = strokeStyle;
  context.fillStyle = fillStyle;
  context.textAlign = &#39;center&#39;;
  context.textBaseline = &#39;hanging&#39;;
  context.strokeText(text, position.x, position.y);
  context.fillText(text, position.x, position.y);
  return canvas.toDataURL(&#39;image/png&#39;);
};

// 使用方式 
<img src="https://xxx.xxx.jpg" />
<p className="warter-mark-area" style={{ backgroundImage: `url(${getBase64Background({})})` }} />
登入後複製

優點:純前端實作方式,支援跨域,支援git 圖水印;

#缺點:生成的base64 url​​ 比較大;

其實根據這兩種canvas 的實現方式可以輕鬆的想出第三種方式,就是在圖片的上層遮一層第一方法中的非圖片的canvas,這樣就能完美的避免兩種方案的缺點。但停留片刻想一下,兩種方案的結合,還是使用 canvas 去繪製,是不是有更簡單易懂的方式呢。對,用 svg 替代。

4,SVG 方式(正在使用的方案)

給出一個 react 版的水印元件。

export const WaterMark = (props) => {
  // 获取水印数据
  const { nick, empId } = GlobalConfig.userInfo;
  const boxRef = React.createRef();
  const [waterMarkStyle, setWaterMarkStyle] = useState(&#39;180px 120px&#39;);
  const [isError, setIsError] = useState(false);
  const {
    src, text = `${nick}-${empId}`, height: propsHeight, showSrc, img, nick, empId
  } = props;
  // 设置背景图和背景图样式
  const boxStyle = {
    backgroundSize: waterMarkStyle,
    backgroundImage: `url("data:image/svg+xml;utf8,<svg width=\&#39;100%\&#39; height=\&#39;100%\&#39; xmlns=\&#39;http://www.w3.org/2000/svg\&#39; version=\&#39;1.1\&#39;><text width=\&#39;100%\&#39; height=\&#39;100%\&#39; x=\&#39;20\&#39; y=\&#39;68\&#39;  transform=\&#39;rotate(-20)\&#39; fill=\&#39;rgba(0, 0, 0, 0.2)\&#39; font-size=\&#39;14\&#39; stroke=\&#39;rgba(255, 255, 255, .2)\&#39; stroke-width=\&#39;1\&#39;>${text}</text></svg>")`,
  };
  const onLoad = (e) => {
    const dom = e.target;
    const {
      previousSibling, nextSibling, offsetLeft, offsetTop,
    } = dom;
    // 获取图片宽高
    const { width, height } = getComputedStyle(dom);
    if (parseInt(width.replace(&#39;px&#39;, &#39;&#39;)) < 180) {
      setWaterMarkStyle(`${width} ${height.replace(&#39;px&#39;, &#39;&#39;) / 2}px`);
    };
    previousSibling.style.height = height;
    previousSibling.style.width = width;
    previousSibling.style.top = `${offsetTop}px`;
    previousSibling.style.left = `${offsetLeft}px`;
    // 加载 loading 隐藏
    nextSibling.style.display = &#39;none&#39;;
  };
  const onError = (event) => {
    setIsError(true);
  };
  return (
    <p className={styles.water_mark_wrapper} ref={boxRef}>
      <p className={styles.water_mark_box} style={boxStyle} />
      {isError
        ? <ErrorSourceData src={src} showSrc={showSrc} height={propsHeight} text="图片加载错误" helpText="点击复制图片链接" />
        : (
          <>
            <img onLoad={onLoad} referrerPolicy="no-referrer" onError={onError} src={src} alt="图片显示错误" />
            <Icon className={styles.img_loading} type="loading" />
          </>
        )
      }
    </p>
  );
};
登入後複製
優點:支援gif 圖水印,不存在跨域問題,使用repeat 屬性,無插入dom 過程,無效能問題;

##QA

問題一:

如果把watermark 的dom 刪除了,圖片不就是無浮水印了嗎?

答案:

可以利用MutationObserver 監聽water 的節點,如果節點被修改,圖片也隨之隱藏;

問題二:

滑鼠右鍵複製圖片?

答案:

全部的圖片都停用了右鍵功能

問題三:

如果從控制台的network取得圖片資訊呢?

答案:

此操作暫時沒有想到好的解決辦法,建議採用後端實作方案

總結

前端實現的水印方案始終只是一種臨時方案,業務後端實現又耗費伺服器資源,其實最理想的解決方式就是提供一個獨立的水印服務,雖然加載過程中會略有延遲,但是相對與資料安全性來說,毫秒級的延遲還是可以接受的,這樣又能保證不影響業務的服務穩定性。

在每天的答疑過程中,也會有很多業務方來找我溝通水印遮擋風險點的問題,每次只能用資料安全的重要性來回复他們,當然,水印的大小,透明度,密集程度也都在不斷的調優中,相信會有一個版本,既能起到水印的作用,也能更好的解決遮擋問題。

推薦學習:html影片教學

以上是你知道前端是如何實現浮水印的嗎的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

PHP與Vue:完美搭檔的前端開發利器 PHP與Vue:完美搭檔的前端開發利器 Mar 16, 2024 pm 12:09 PM

PHP與Vue:完美搭檔的前端開發利器在當今網路快速發展的時代,前端開發變得愈發重要。隨著使用者對網站和應用的體驗要求越來越高,前端開發人員需要使用更有效率和靈活的工具來創建響應式和互動式的介面。 PHP和Vue.js作為前端開發領域的兩個重要技術,搭配起來可以稱得上是完美的利器。本文將探討PHP和Vue的結合,以及詳細的程式碼範例,幫助讀者更好地理解和應用這兩

如何使用 Go 語言進行前端開發? 如何使用 Go 語言進行前端開發? Jun 10, 2023 pm 05:00 PM

隨著網路技術的發展,前端開發變得日益重要。尤其是行動端設備的普及,更需要高效率、穩定、安全又易於維護的前端開發技術。而作為一門快速發展的程式語言,Go語言已經被越來越多的開發者所使用。那麼,使用Go語言進行前端開發行得通嗎?接下來,本文將為你詳細說明如何使用Go語言進行前端開發。先來看看為什麼要使用Go語言進行前端開發。很多人認為Go語言是一門

C#開發經驗分享:前端與後端協同開發技巧 C#開發經驗分享:前端與後端協同開發技巧 Nov 23, 2023 am 10:13 AM

身為C#開發者,我們的開發工作通常包括前端和後端的開發,而隨著技術的發展和專案的複雜性提高,前端與後端協同開發也變得越來越重要和複雜。本文將分享一些前端與後端協同開發的技巧,以幫助C#開發者更有效率地完成開發工作。確定好介面規範前後端的協同開發離不開API介面的交互。要確保前後端協同開發順利進行,最重要的是定義好介面規格。接口規範涉及到接口的命

前端面試官常問的問題 前端面試官常問的問題 Mar 19, 2024 pm 02:24 PM

在前端開發面試中,常見問題涵蓋廣泛,包括HTML/CSS基礎、JavaScript基礎、框架和函式庫、專案經驗、演算法和資料結構、效能最佳化、跨域請求、前端工程化、設計模式以及新技術和趨勢。面試官的問題旨在評估候選人的技術技能、專案經驗以及對行業趨勢的理解。因此,應試者應充分準備這些方面,以展現自己的能力和專業知識。

Django是前端還是後端?一探究竟! Django是前端還是後端?一探究竟! Jan 19, 2024 am 08:37 AM

Django是一個由Python編寫的web應用框架,它強調快速開發和乾淨方法。儘管Django是web框架,但要回答Django是前端還是後端這個問題,需要深入理解前後端的概念。前端是指使用者直接和互動的介面,後端是指伺服器端的程序,他們透過HTTP協定進行資料的互動。在前端和後端分離的情況下,前後端程式可以獨立開發,分別實現業務邏輯和互動效果,資料的交

前端怎麼實現即時通訊 前端怎麼實現即時通訊 Oct 09, 2023 pm 02:47 PM

實作即時通訊的方法有WebSocket、Long Polling、Server-Sent Events、WebRTC等等。詳細介紹:1、WebSocket,它可以在客戶端和伺服器之間建立持久連接,實現即時的雙向通信,前端可以使用WebSocket API來創建WebSocket連接,並透過發送和接收訊息來實現即時通訊;2、Long Polling,是一種模擬即時通訊的技術等等

Go語言前端技術探秘:前端開發新視野 Go語言前端技術探秘:前端開發新視野 Mar 28, 2024 pm 01:06 PM

Go語言作為一種快速、高效的程式語言,在後端開發領域廣受歡迎。然而,很少有人將Go語言與前端開發聯繫起來。事實上,使用Go語言進行前端開發不僅可以提高效率,還能為開發者帶來全新的視野。本文將探討使用Go語言進行前端開發的可能性,並提供具體的程式碼範例,幫助讀者更了解這一領域。在傳統的前端開發中,通常會使用JavaScript、HTML和CSS來建立使用者介面

Django:前端和後端開發都能搞定的神奇框架! Django:前端和後端開發都能搞定的神奇框架! Jan 19, 2024 am 08:52 AM

Django:前端和後端開發都能搞定的神奇框架! Django是一個高效、可擴展的網路應用程式框架。它能夠支援多種Web開發模式,包括MVC和MTV,可以輕鬆地開發出高品質的Web應用程式。 Django不僅支援後端開發,還能夠快速建構出前端的介面,透過模板語言,實現靈活的視圖展示。 Django把前端開發和後端開發融合成了一種無縫的整合,讓開發人員不必專門學習

See all articles