纯canvas 实现多行水印,支持vue,react等框架

439 阅读3分钟

页面效果

image.png

实现步骤

  1. 获取Canvas元素,并设置其宽高为屏幕宽高,同时将像素比乘以窗口设备像素比,以保证显示效果正确。
  2. 设置水印的样式,包括颜色、字体大小、倾斜角度、行高、左右边距、上下边距和垂直间距等参数。
  3. 编写绘制水印的函数drawWatermark(),设置水印文本,并进行旋转、分行等处理,最后使用循环生成每个水印的位置坐标,并在对应位置绘制水印文本。

代码展示

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>Canvas 水印</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        html,
        body {
            height: 100%;
            margin: 0;
            padding: 0;
        }

        canvas {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 200%;
            height: 200%;
            z-index: -1;
        }
    </style>
</head>

<body>
    <canvas id="canvas"></canvas>
    <!-- 页面内容 -->
    <div>
        <h1>Canvas 水印示例</h1>
        <p>这是一个基于 Canvas 实现的水印效果,可以在多行文本之间设置间距,并倾斜显示。</p>
    </div>
    <!-- JS 代码 -->
    <script>
        var canvas = document.getElementById('canvas');
        var ctx = canvas.getContext('2d');

        // 设置 canvas 元素的宽度和高度为屏幕的宽度和高度
        canvas.width = window.innerWidth * window.devicePixelRatio;
        canvas.height = window.innerHeight * window.devicePixelRatio;

        // 水印样式
        var color = 'rgba(0, 0, 0, 0.2)';
        var fontSize = 12 * window.devicePixelRatio;
        var angle = -45 * Math.PI / 180;
        var lineHeight = 30 * window.devicePixelRatio; // 调整行高时也要乘以设备像素比
        var marginLeft = 10 * window.devicePixelRatio;
        var marginTop = 30 * window.devicePixelRatio;
        var verticalOffset = 50 * window.devicePixelRatio; // 调整垂直间距时也要乘以设备像素比

        // 绘制水印
        function drawWatermark() {
            var text = '这是水印示例\n多行展示内容';
            ctx.fillStyle = color;
            ctx.font = fontSize + 'px Arial';
            ctx.rotate(angle);

            var lines = text.split('\n');
            var numLines = Math.ceil(canvas.height / lineHeight); //计算出在canvas高度范围内可以容纳多少行水印
            var lineWidth = Math.max(...lines.map(line => ctx.measureText(line).width)); // 获取最长的水印宽度

            // 计算一行可以绘制几列水印
            var numCols = Math.ceil(canvas.width / (lineWidth + marginLeft));
            var offset = Math.ceil( canvas.width / 2); //计算偏移量,用于调整水印位置

            for (var i = 0; i < numLines; i++) {
                for (var j = 0; j < numCols; j++) {
                    lines.forEach(function (line, index) {
                        var x = (j * (lineWidth + marginLeft)) + marginLeft - offset;
                        var y = (i * lines.length + index + 1) * lineHeight + marginTop + (i * verticalOffset);
                        ctx.fillText(line, x, y);
                    });
                }
            }
        }

        // 初始化绘制
        drawWatermark();

        // 当浏览器窗口大小改变时,重新绘制水印
        window.addEventListener('resize', function () {
            canvas.width = window.innerWidth; // 更新 canvas 的宽度
            canvas.height = window.innerHeight; // 更新 canvas 的高度
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            drawWatermark();
        });
    </script>
</body>

</html>

代码解释

var y = (i * lines.length + index + 1) 
    * 
    lineHeight + marginTop
    +
    (i * verticalOffset); 
  • i * lines.length: 这部分是为了确保每行水印在垂直方向上有一定的间距,通过将每行的高度乘以总行数,来确定当前行的基础位置。
  • index + 1: 这部分是为了让每行多行水印中的每行都有一定的垂直偏移,确保多行水印之间不会重叠。
  • lineHeight: 表示每行水印的高度,即每行文字的行高。
  • marginTop: 是水印距离画布顶部的距离,用于调整水印的垂直起始位置。
  • i * verticalOffset: 这部分是为了让每列水印在垂直方向上也有一定的间距,通过将每列的垂直偏移乘以列数,来确定当前列的基础位置。

vue 方式


<template>
  <div>
    <canvas id="canvas"></canvas>
  </div>
</template>

<script>
export default {
  mounted() {
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');

    // 设置 canvas 元素的宽度和高度为屏幕的宽度和高度
    canvas.width = window.innerWidth * window.devicePixelRatio;
    canvas.height = window.innerHeight * window.devicePixelRatio;

    // 水印样式...
    // 省略水印样式部分的代码

    // 绘制水印
    function drawWatermark() {
      // 绘制水印的代码...
      // 省略绘制水印部分的代码
    }

    // 初始化绘制
    drawWatermark();

    // 当浏览器窗口大小改变时,重新绘制水印
    window.addEventListener('resize', function () {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      drawWatermark();
    });
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.drawWatermark);
  }
};
</script>

react 方式

import React, { useEffect } from 'react';

const Watermark = () => {
  useEffect(() => {
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');

    // 设置 canvas 元素的宽度和高度为屏幕的宽度和高度
    canvas.width = window.innerWidth * window.devicePixelRatio;
    canvas.height = window.innerHeight * window.devicePixelRatio;

    // 水印样式...
    // 省略水印样式部分的代码

    // 绘制水印
    const drawWatermark = () => {
      // 绘制水印的代码...
      // 省略绘制水印部分的代码
    };

    // 初始化绘制
    drawWatermark();

    // 当浏览器窗口大小改变时,重新绘制水印
    const handleResize = () => {
      canvas.width = window.innerWidth;
      canvas.height = window.innerHeight;
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      drawWatermark();
    };
    window.addEventListener('resize', handleResize);

    // 在组件销毁时移除事件监听器
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []); // 注意第二个参数是空数组,表示effect不依赖于props或state,因此仅在组件挂载和卸载时执行

  return <canvas id="canvas"></canvas>;
};

export default Watermark;