xuanweiH / Project-issue

记录项目遇到一些问题与封装
2 stars 0 forks source link

前端如何实现水印生成 #14

Open xuanweiH opened 3 years ago

xuanweiH commented 3 years ago

话不多说直接上代码:

/**
 * renderWaterMark
 * 生成水印
 * @param {Object}
 * */
export function renderWaterMark({
  container = document.body,
  width = "100px",
  height = "100px",
  textAlign = "center",
  textBaseline = "middle",
  font = "20px microsoft yahei",
  fillStyle = "rgba(184, 184, 184, 0.1)",
  content = "请勿外传",
  rotate = "30",
  zIndex = 9999
} = {}) {
  const canvas = document.createElement("canvas");
  canvas.setAttribute("width", width);
  canvas.setAttribute("height", height);
  const ctx = canvas.getContext("2d");

  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 renderWaterMarkDiv = document.createElement("div");
  renderWaterMarkDiv.id = "waterMark";
  renderWaterMarkDiv.setAttribute(
    "style",
    `
          position:absolute;
          top:0;
          left:0;
          width:100%;
          height:100%;
          z-index:${zIndex};
          pointer-events:none;
          background-repeat:repeat;
          background-image:url('${base64Url}')`
  );

  // container.style.position = 'relative';
  container.insertBefore(renderWaterMarkDiv, container.firstChild);
}
xuanweiH commented 3 years ago

增加了水印的防修改功能 优化了之前的版本

// 首先定义一个接口类 限制options的类型
export interface WaterMarkOption {
  width?: string;
  height?: string;
  textAlign?: CanvasTextAlign;
  textBaseline?: CanvasTextBaseline;
  font?: string;
  fillStyle?: string;
  content?: string;
  rotate?: number;
  zIndex?: number;
}

// 创建一个cavas对象来画水印
async function getWatermarkDom({
  width = "100px",
  height = "100px",
  textAlign = "center",
  textBaseline = "middle",
  font = "20px microsoft yahei",
  fillStyle = "rgba(184, 184, 184, 0.1)",
  content = "请勿外传",
  rotate = 30,
  zIndex = 9999
}: WaterMarkOption = {}) {
  const canvas = document.createElement("canvas");
  canvas.setAttribute("width", width);
  canvas.setAttribute("height", height);
  const ctx = canvas.getContext("2d");
  if (!ctx) return;
  ctx.save();
  ctx.textAlign = textAlign;
  ctx.textBaseline = textBaseline;
  ctx.font = font;
  ctx.fillStyle = fillStyle;
  // 把画的图像稍微翻转一下角度
  ctx.translate(parseFloat(width) / 2, parseFloat(height) / 2);
  ctx.rotate((Math.PI / 180) * rotate);
  ctx.translate(-parseFloat(width) / 2, -parseFloat(height) / 2);
  ctx.fillText(content, parseFloat(width) / 2, parseFloat(height) / 2);
  ctx.translate(parseFloat(width) / 2, parseFloat(height) / 2);
  ctx.rotate((-Math.PI / 180) * rotate);
  ctx.translate(-parseFloat(width) / 2, -parseFloat(height) / 2);
  // restore() 方法从栈中弹出存储的图形状态并恢复 CanvasRenderingContext2D 对象的属性
  ctx.restore();
  // cavas画好以后转为blob再获取url
  const getUrl = () =>
    new Promise(resolve => {
      canvas.toBlob(blob => {
        resolve(window.URL.createObjectURL(blob));
      });
    });

  const waterMarkUrl = await getUrl();

  const renderWaterMarkDiv = document.createElement("div");
  renderWaterMarkDiv.id = window.btoa("water-mark_" + Date.now());
  renderWaterMarkDiv.setAttribute(
    "style",
    `
      position:absolute;
      top:0;
      left:0;
      width:100%;
      height:100%;
      z-index:${zIndex};
      pointer-events:none;
      background-repeat:repeat;
      background-image:url('${waterMarkUrl}')`
  );

  return renderWaterMarkDiv;
}
// 完成绘制以后再来一个防修改处理
function securityDefense(target) {
  // 把canvas画好的div导入
  document.body.appendChild(target);
  const callback = function(records) {
  // 如果有相关操作先移除target 再添加回来防止删除
    if (
      records.length === 1 &&
      ((records[0].target === target && records[0].attributeName === "style") ||
        (records[0].removedNodes.length >= 1 &&
          records[0].removedNodes[0] === target))
    ) {
      if (records[0].attributeName === "style") {
        document.body.removeChild(target);
      }
      bodyObserver.disconnect();
      domObserver.disconnect();
      setTimeout(() => createWaterMark(), 0);
    }
  };
  // 先创建一个全局观察者 观察body的子标签 防止用户直接去element里面删除
  const MutationObserver = window.MutationObserver;
  const bodyObserver = new MutationObserver(callback);
  bodyObserver.observe(document.body, {
    childList: true
  });
  // 再创建一个目标节点观察者,观察是否修改style属性
  const domObserver = new MutationObserver(callback);
  domObserver.observe(target, { attributes: true, attributeFilter: ["style"] });
}

let _option = {};
export default function createWaterMark(options?: WaterMarkOption) {
  _option = options ? options : _option;
  getWatermarkDom(_option).then(securityDefense);
}

--------------------

vuex user.js中
import createWaterMark from "@/utils/waterMark";
// 把userid作为背景当做水印
 SET_USER_DATA(state, data) {
      createWaterMark({ content: data.uid });
      localStorage.setItem(USER_DATA_KEY, JSON.stringify(data));
      state.data = data;
    },