Skip to content
大纲

防改水印

利用 canvas 创建文字背景,然后生成 mark 容器。需要使用的地方,插入这个容器,监听这个父容器的修改,然后做重新生成容器。

实现

ts
type optionsType = {
  text: string;
  fontSize: number;
  color: string;
  gap: number;
  opacity: number;
  font?: string;
};

type cnavasDataType = {
  base64: string;
  size: number;
  styleSize: number;
  opacity: number;
};

const createCanvasText = (options: optionsType) => {
  const canvas = document.createElement("canvas");
  const devicePxielRatio = window.devicePixelRatio || 1;
  const fontSize = (options.fontSize || 16) * devicePxielRatio;
  const font = fontSize + `px ${options.font || "serif"}`;
  const ctx = canvas.getContext("2d");

  if (!ctx) return;
  ctx.font = font;
  const { width } = ctx.measureText(options.text || "mark"); // 获取文本宽度信息
  const canvasSize = Math.max(100, width) + options.gap * devicePxielRatio;
  canvas.width = canvasSize;
  canvas.height = canvasSize;
  ctx.translate(canvas.width / 2, canvas.height / 2);
  ctx.rotate((Math.PI / 180) * -45);
  ctx.fillStyle = options.color;
  ctx.font = font;
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  ctx.fillText(options.text || "mark", 0, 0);
  return {
    base64: canvas.toDataURL(),
    size: canvasSize,
    styleSize: canvasSize / devicePxielRatio,
    opacity: options.opacity,
  };
};

const creatSectionWrap = (canvasText: cnavasDataType) => {
  const section = document.createElement("section");
  section.style.backgroundImage = `url(${canvasText?.base64})`;
  section.style.backgroundSize = `${canvasText?.styleSize}px ${canvasText?.styleSize}px `;
  section.style.backgroundRepeat = "repeat";
  section.style.zIndex = "99999999";
  section.style.position = "absolute";
  section.style.inset = "0";
  section.style.opacity = (canvasText.opacity || 1) + "";

  return section;
};

export const createWatermark = (options: optionsType) => {
  const canvasText = createCanvasText(options) as cnavasDataType;
  let ob: MutationObserver;
  let p: HTMLElement;
  let section: HTMLElement | null;
  return {
    create: (parent: HTMLElement) => {
      section = creatSectionWrap(canvasText);
      parent.appendChild(section);
      p = parent;
      ob = new MutationObserver((records) => {
        for (const el of records) {
          if (el.type === "attributes") {
            if (el.target === section) {
              parent.removeChild(section);
              return;
            }
          }

          if (
            el.type === "childList" &&
            el.removedNodes.length &&
            el.target === parent
          ) {
            el.removedNodes.forEach((e) => {
              if (e === section) {
                section = creatSectionWrap(canvasText);
                parent.appendChild(section);
              }
            });
          }
        }
      });

      ob.observe(parent, {
        childList: true,
        attributes: true,
        subtree: true,
      });
    },
    remove: () => {
      ob && ob.disconnect();
      section && p.removeChild(section);
      section = null;
    },
    clear: () => {
      ob && ob.disconnect();
      section = null;
    },
  };
};

export default createWatermark;

使用

js
const wrap = ref<HTMLElement>()
const { create, clear, remove } = createWatermark({ fontSize: 20, text: "maps1313", color: 'red', gap: 20, opacity: 0.6 })

const createMark = () => {
  if (!wrap.value) return
  create(wrap.value)
}

onBeforeUnmount(() => {
  clear()
})

Released under the MIT License.