自適應且不可刪除的水印蒙層

2022-12-08 15:01:48

canvas自適應文字長度,旋轉角度生成水印背景圖

  • 設定canvas字型大小後,通過ctx.measureText(text).width獲取兩行文字的寬度text1,text2,取最大寬度為文字方塊寬度textWidth
  • 設定兩行文字間距,可得文字方塊高度:textHeight=2*fontsize+ space_line
  • 計算最小一個能夠完全包裹旋轉後文字的盒子寬高
    已知旋轉角度為rotate=>得到弧度rad = (rotate*Math.pi) /180
    單個水印圖平鋪成為蒙層的背景圖,space_x,space_y用於調整水印之間的間距
  function  drawWatermark(el, config = {}) {
      if (!el) return;
      // 預設設定
      let {
        text1 = '今天也要保持愉悅鴨~', //文字1
        text2 = '2022-12-07', // 文字2
        space_x = 0, // x軸間距 
        space_y = 0, // y軸間距
        space_line = 20, //兩列文字的間距
        font = 'Microsoft JhengHei bold',
        fontSize = 40, // 字型
        color = 'rgba(22,22,22,1)', // 字色
        rotate = 30 // 傾斜度
      } = config;
      const canvas =  document.createElement('canvas');
      el.appendChild(canvas);
      const ctx = canvas .getContext('2d');
      ctx.font = fontSize + 'px ' + font; //設定好fontsize才能正確計算出文字寬度
      let tw1= ctx.measureText(text1).width;
      let tw2= ctx.measureText(text2).width;
      let textWidth = Math.max(tw1, tw2); //文字最長寬度為文字方塊寬度
      let textHeight = fontSize * 2 + space_line; //文字方塊高度為兩個文字+行間距
      let rad  = (rotate * Math.PI) / 180; //角度轉弧度
      let sin = Math.sin(rad ); 
      let cos = Math.cos(rad );
      let width = textWidth * cos + textHeight * sin + space_x ; //為包裹住文字方塊的最小盒子寬度
      let height = textWidth * sin + textHeight * cos + space_y; //為包裹住文字方塊的最小盒子高度
      canvas.width = width;
      canvas.height = height;
      canvas.style.cssText = `width:${width}px;height:${height}px;display:none;`;
      ctx.translate(space_x , textWidth * sin + space_y );  // 移動旋轉中心
      ctx.rotate((-1 * (rotate * Math.PI)) / 180); //旋轉文字方塊
      ctx.fillStyle = color;
      ctx.textAlign = 'left';
      ctx.textBaseline = 'middle';
      ctx.font = fontSize + 'px ' + font;
      ctx.fillText(text1, (textWidth - tw1) / 2, 0.5 * fontSize);  //文字在文字方塊中居中顯示
      ctx.fillText(text2, (textWidth - tw2) / 2, 1.5 * fontSize + space_line); //文字在文字方塊中居中顯示
      return canvas.toDataURL('image/png');
    },

生成蒙層

在目標元素下新增一個相對定位的子元素,將水印圖片平鋪作為背景圖。

禁止蒙層的刪除和修改

  • 刪除或移動element
  • 修改style

transform: translate(100%,100%);
display: none;
visibility: hidden;
取消背景圖

 function createMask(el) {
      //建立蒙層
      let $mask = document.createElement('div');
      //判斷蒙層父元素是否有定位
      let position = window.getComputedStyle(el, null).position;
      if (position === 'static') {
        el.style.position = 'relative';
      }
      //設定蒙層樣式
      let style = `visibility: visible !important;
        transform: translate(0,0)  !important;
        display: block !important;
        visibility: visible !important;
        width: 100% !important; 
        height: 100% !important;
        pointer-events: none !important; 
        background-color: rgba(0, 0, 0, 0)!important;
        background-repeat: repeat !important;
        position: absolute !important;
        top: 0px !important;
        left: 0px !important;
        z-index: 999 !important; 
        background-image: url(${drawWatermark(el)}) !important`;
      $mask.setAttribute('style', style);
      //新增蒙層
      el.append($mask);
      // 建立MutationObserver
      el.observer = new MutationObserver((mutationRecord) => {
        //處理DOM
        mutationRecord.forEach((mutation) => {
          // 蒙層刪除或者被移動到別處
          if (mutation.target === el && mutation.removedNodes[0] == $mask) {
            el.append($mask);
          } else if (mutation.target == $mask && mutation.attributeName === 'style') {
            // 蒙層被更改樣式 在監聽到蒙層樣式更改後,賦值的新的樣式會導致再次觸發監聽回撥,所以需要在監聽事件中判斷何時需要賦值
            const changestyle = $mask?.getAttribute('style');
            if (changestyle !== style) {
              $mask.setAttribute('style', style);
            }
          }
        });
      });
      // 啟動監控
      el.observer.observe(el, {
        childList: true,
        attributes: true,
        subtree: true
      });
      return $mask;
    },

行內樣式加important是為了防止通過新增class或其他css覆蓋樣式(暫時沒有找到怎麼如何通過修改css的方式更改樣式的監聽方式)
踩得一個坑
設定元素的行內樣式有很多種

let style = 'width:100%;height:100%' 適用單個樣式更改
element.style.width =100% ;element.style['height'] =100%
element.style.cssText = style
element.setAttribute('style',style )

方式二設定樣式後,行內樣式格式和賦值時的style的格式不一樣,獲取到行內style後直接進行===判斷,回造成死迴圈

解決方法:

  • 第一次監聽到蒙層更改時,立刻移除蒙層,重新生成新蒙層
  • 寫個函數判斷不同格式的兩個樣式屬性上是否相等