前端水印生成方案

先看下效果:


水印效果

思路1:

使用canvas進(jìn)行生成圖片,然后動(dòng)態(tài)生成div填充整個(gè)背景,將生成的圖片用與 background-image屬性上,進(jìn)行頁面填充.
問題:
如果在開發(fā)者工具里取消了這個(gè)屬性,則該水印效果就會(huì)消失,所以需要一種方法監(jiān)視該div,不讓其修改,或修改了之后又進(jìn)行恢復(fù),此處就用到了用來監(jiān)視 DOM變動(dòng)的Mutation Observer API .DOM 的任何變動(dòng),比如節(jié)點(diǎn)的增減柳刮、屬性的變動(dòng)挖垛、文本內(nèi)容的變動(dòng),這個(gè) API 都可以得到通知秉颗。參考MDN:MutationObserver
MutationObserver只能監(jiān)測到諸如屬性改變痢毒、增刪子結(jié)點(diǎn)等,對(duì)于自己本身被刪除蚕甥,是沒有辦法的,那么可以通過監(jiān)測父結(jié)點(diǎn)來達(dá)到要求哪替。

代碼:


/**
 * 網(wǎng)頁加水印
 *
 * @export
 * @param {*} [{
 *   container = document.body,
 *   width = '400px',
 *   height = '300px',
 *   textAlign = 'center',
 *   textBaseline = 'middle',
 *   font = "20px Microsoft Yahei",
 *   fillStyle = 'rgba(230, 230, 230, 0.8)',
 *   content = '保密水印',
 *   rotate = '10',
 *   zIndex = -1000
 * }={}]
 * @returns
 */
export function __waterDocument({
  container = document.body,
  width = '200px',
  height = '150px',
  textAlign = 'center',
  textBaseline = 'middle',
  font = "20px Microsoft Yahei",
  fillStyle = 'rgba(230, 230, 230, 0.8)',
  content = '保密水印',
  rotate = '10',
  zIndex = -1000
} = {}) {
  const args = arguments[0];
  const canvas = document.createElement('canvas');
  canvas.setAttribute('width', width);
  canvas.setAttribute('height', height);
  const ctx = canvas.getContext("2d");
  if (ctx === null) {
    console.error("this browser is not support canvas.");
    return;
  }
  ctx.textAlign = textAlign;
  ctx.textBaseline = textBaseline;
  ctx.font = font;
  ctx.fillStyle = fillStyle;
  ctx.rotate(Math.PI / 180 * rotate);
  ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2);
  const base64Url = canvas.toDataURL();
  const __wm = document.querySelector('.__wm');
  const watermarkDiv = __wm || document.createElement("div");
  const styleStr = `
      position:absolute;
      top:0;
      left:0;
      width:100%;
      height:100%;
      z-index:${zIndex};
      pointer-events:none;
      background-repeat:repeat;
      background-image:url('${base64Url}')`;
  watermarkDiv.setAttribute('style', styleStr);
  watermarkDiv.classList.add('__wm');
  if (!__wm) {
    container.style.position = 'relative';
    container.insertBefore(watermarkDiv, container.firstChild);
  }
  const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
  if (MutationObserver) {
    let mo = new MutationObserver(function () {
      const __wm = document.querySelector('.__wm');
      // 只在__wm元素變動(dòng)才重新調(diào)用 __canvasWM
      if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm) {
        // 避免一直觸發(fā)
        mo.disconnect();
        mo = null;
        __canvasWM(JSON.parse(JSON.stringify(args)));
      }
    });
    mo.observe(container, {
      attributes: true,
      subtree: true,
      childList: true
    })
  }
}

思路2:

如果瀏覽器對(duì)canvas不支持則可以使用svg,svg瀏覽器支持面比canvas要大一些
可以使用https://caniuse.com/進(jìn)行查看
實(shí)現(xiàn)原理與canvas幾乎一致,只是圖片替換成了svg


/**
 *網(wǎng)頁加水印 svg 方式
 *
 * @export
 * @param {*} [{
 *   container = document.body,
 *   content = '請(qǐng)勿外傳',
 *   width = '300px',
 *   height = '200px',
 *   opacity = '0.2',
 *   fontSize = '20px',
 *   zIndex = 1000
 * }={}]
 */
export function __waterDocumentSvg({
  container = document.body,
  content = '請(qǐng)勿外傳',
  width = '300px',
  height = '200px',
  opacity = '0.2',
  fontSize = '20px',
  zIndex = 1000
} = {}) {
  const args = arguments[0];
  const svgStr = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${width}">
<text x="50%" y="50%" dy="12px"
text-anchor="middle"
stroke="#000000"
stroke-width="1"
stroke-opacity="${opacity}"
fill="none"
transform="rotate(-45, 120 120)"
style="font-size: ${fontSize};">
${content}
</text>
</svg>`;
  const base64Url = `data:image/svg+xml;base64,${window.btoa(unescape(encodeURIComponent(svgStr)))}`;
  const __wm = document.querySelector('.__wm');
  const watermarkDiv = __wm || document.createElement("div");
  const styleStr = `
    position:absolute;
    top:0;
    left:0;
    width:100%;
    height:100%;
    z-index:${zIndex};
    pointer-events:none;
    background-repeat:repeat;
    background-image:url('${base64Url}')`;
  watermarkDiv.setAttribute('style', styleStr);
  watermarkDiv.classList.add('__wm');
  if (!__wm) {
    container.style.position = 'relative';
    container.insertBefore(watermarkDiv, container.firstChild);
  }
  const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
  if (MutationObserver) {
    let mo = new MutationObserver(function () {
      const __wm = document.querySelector('.__wm');
      // 只在__wm元素變動(dòng)才重新調(diào)用 __canvasWM
      if ((__wm && __wm.getAttribute('style') !== styleStr) || !__wm) {
        // 避免一直觸發(fā)
        mo.disconnect();
        mo = null;
        __waterDocumentSvg(JSON.parse(JSON.stringify(args)));
      }
    });
    mo.observe(container, {
      attributes: true,
      subtree: true,
      childList: true
    })
  }
}

通過NodeJS生成水印

身為現(xiàn)代前端開發(fā)者,Node.JS也是需要掌握的菇怀。我們同樣可以通過NodeJS來生成網(wǎng)頁水印,前端發(fā)一個(gè)請(qǐng)求凭舶,參數(shù)帶上水印內(nèi)容,后臺(tái)返回圖片內(nèi)容爱沟。
具體實(shí)現(xiàn)(Koa2環(huán)境):

  1. 安裝gm以及相關(guān)環(huán)境帅霜,詳情看gm文檔
  2. ctx.type = 'image/png';設(shè)置響應(yīng)為圖片類型
  3. 生成圖片過程是異步的,所以需要包裝一層Promise呼伸,這樣才能為通過 async/await 方式為 ctx.body 賦值
const fs = require('fs')
const gm = require('gm');
const imageMagick = gm.subClass({
  imageMagick: true
});


const router = require('koa-router')();

router.get('/wm', async (ctx, next) => {
  const {
    text
  } = ctx.query;

  ctx.type = 'image/png';
  ctx.status = 200;
  ctx.body = await ((() => {
    return new Promise((resolve, reject) => {
      imageMagick(200, 100, "rgba(255,255,255,0)")
        .fontSize(40)
        .drawText(10, 50, text)
        .write(require('path').join(__dirname, `./${text}.png`), function (err) {
          if (err) {
            reject(err);
          } else {
            resolve(fs.readFileSync(require('path').join(__dirname, `./${text}.png`)))
          }
        });
    })
  })());
});

圖片水印生成解決方案

通過canvas給圖片加水印
思路:讀取圖片,在圖片加載完之后用canvas填充圖片,并填充加密文字,然后將圖片轉(zhuǎn)成base64url傳值給回調(diào)函數(shù)

/**
 *圖片加水印
 *
 * @export
 * @param {*} [{
 *   url = '',
 *   textAlign = 'center',
 *   textBaseline = 'middle',
 *   font = "20px Microsoft Yahei",
 *   fillStyle = 'rgba(184, 184, 184, 0.8)',
 *   content = '請(qǐng)勿外傳',
 *   cb = null,
 *   textX = 100,
 *   textY = 30
 * }={}]
 */
export function __picWM({
  url = '',
  textAlign = 'center',
  textBaseline = 'middle',
  font = "20px Microsoft Yahei",
  fillStyle = 'rgba(184, 184, 184, 0.8)',
  content = '請(qǐng)勿外傳',
  cb = null,
  textX = 100,
  textY = 30
} = {}) {
  const img = new Image();
  img.src = url;
  img.crossOrigin = 'anonymous';
  img.onload = function () {
    const canvas = document.createElement('canvas');
    canvas.width = img.width;
    canvas.height = img.height;
    const ctx = canvas.getContext('2d');
    ctx.drawImage(img, 0, 0);
    ctx.textAlign = textAlign;
    ctx.textBaseline = textBaseline;
    ctx.font = font;
    ctx.fillStyle = fillStyle;
    ctx.fillText(content, img.width - textX, img.height - textY);
    const base64Url = canvas.toDataURL();
    cb && cb(base64Url);
  }
}

調(diào)用:

__picWM({
        url: 'https://www.baidu.com/img/bd_logo1.png?where=super',
        content: '加密圖片',
        cb: (base64Url) => {
          document.getElementById('s_lg_img').src = base64Url
        },
      })

效果:


加密圖片

使用加密后的水印內(nèi)容

前端生成的水印也可以身冀,別人也可以用同樣的方式生成,可能會(huì)有“嫁禍于人”(可能這是多慮的),我們還是要有更安全的解決方法搂根。水印內(nèi)容可以包含多種編碼后的信息珍促,包括用戶名、用戶ID兄墅、時(shí)間等踢星。比如我們只是想保存用戶唯一的用戶ID,需要把用戶ID傳入下面的md5方法隙咸,就可以生成唯一標(biāo)識(shí)沐悦。編碼后的信息是不可逆的,但可以通過全局遍歷所有用戶的方式進(jìn)行追溯五督。這樣就可以防止水印造假也可以追溯真正水印的信息藏否。

// MD5加密庫 utility
const utils = require('utility')

// 加鹽MD5
exports.md5 =  function (content) {
  const salt = 'microzz_asd!@#IdSDAS~~';
  return utils.md5(utils.md5(content + salt));
}

參考:前端水印生成方案 --QQ音樂前端團(tuán)隊(duì)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市充包,隨后出現(xiàn)的幾起案子副签,更是在濱河造成了極大的恐慌,老刑警劉巖基矮,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件淆储,死亡現(xiàn)場離奇詭異,居然都是意外死亡家浇,警方通過查閱死者的電腦和手機(jī)本砰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钢悲,“玉大人点额,你說我怎么就攤上這事≥毫眨” “怎么了还棱?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長惭等。 經(jīng)常有香客問我珍手,道長,這世上最難降的妖魔是什么辞做? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任珠十,我火速辦了婚禮,結(jié)果婚禮上凭豪,老公的妹妹穿的比我還像新娘焙蹭。我一直安慰自己,他們只是感情好嫂伞,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布孔厉。 她就那樣靜靜地躺著拯钻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撰豺。 梳的紋絲不亂的頭發(fā)上市埋,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天昌跌,我揣著相機(jī)與錄音褐桌,去河邊找鬼书幕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凡橱,可吹牛的內(nèi)容都是我干的小作。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼稼钩,長吁一口氣:“原來是場噩夢啊……” “哼顾稀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起坝撑,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤静秆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后巡李,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抚笔,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年侨拦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了塔沃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡阳谍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出螃概,到底是詐尸還是另有隱情矫夯,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布吊洼,位于F島的核電站训貌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏冒窍。R本人自食惡果不足惜递沪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望综液。 院中可真熱鬧款慨,春花似錦、人聲如沸谬莹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至埠戳,卻和暖如春井誉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背整胃。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工颗圣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人屁使。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓在岂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親屋灌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子洁段,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,506評(píng)論 25 707
  • 大部分的人都會(huì)對(duì)聽故事感興趣,當(dāng)講故事的人共郭,說的栩栩如生祠丝,身臨其境,一定會(huì)讓我們有所共鳴除嘹,而如果講故...
    盆叔閱讀 235評(píng)論 0 0
  • 我沒喝過最烈的酒写半,但我曾經(jīng)醉的荒唐。 我沒走過最遠(yuǎn)的路尉咕,但我曾經(jīng)在某個(gè)人身后嘶吼追趕叠蝇。 我...
    獨(dú)尊劉子耕閱讀 225評(píng)論 0 0
  • 一、目標(biāo) 第一時(shí)間讓我們更明確什么是目標(biāo)年缎。目標(biāo)是自己非常想要悔捶,不惜一切代價(jià)都要去得到或者實(shí)現(xiàn)的事物。以九宮格的方式...
    瑜悅?cè)松?/span>閱讀 714評(píng)論 0 0