前段水印 React 组件实现方式

232 阅读1分钟

最近在公司项目里接触了前端水印相关的需求,在这里分享一下如何把水印封装成组件吧~

分类

我了解到的水印实现方式有三种:div遮罩、svg背景图、canvas背景图,我这里简单讲下后两种。

svg

话不多说,直接上代码

// 这里是因为原生 btoa 不能转义中文字符
function Btoa(text: string) {
    return btoa(unescape(encodeURIComponent(text)));
}

function generateSVGBase64(props) {
    const { width, height, color, rotate, fontSize, fontFamily } = props;
    let { text } = this.props;

    if (Array.isArray(text)) {
        if (text.length === 1) {
            text = text[0];
        } else {
            text = text
                .map((item, index) => `<tspan x="0" y="${(index + 1) * fontSize}">${item}</tspan>`)
                .join('');
        }
    }

    const svg = `
        <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
            width="100%" height="100%">
            <style type="text/css">
                text {
                    fill: ${color};
                    stroke: ${color};
                    font-size: ${fontSize}px;
                    font-family: ${fontFamily};
                }
            </style>
            <defs>
                <pattern id="combo" width="${width}" height="${height}"
                    patternTransform="rotate(${rotate})" patternUnits="userSpaceOnUse">
                    <text>${text}</text>
                </pattern>
            </defs>
            <rect width="100%" height="100%" fill="url(#combo)"/>
        </svg>`;
    return `url('data:image/svg+xml;base64,${Btoa(svg.trim())}')`;
}

export default function Watermark(props) {
    let { children, className, style } = props;
    const backgroundImage = generateSVGBase64(props);
    style = {
        backgroundImage,
        ...style,
    };
    return (
        <div className={className} style={style}>
            {children}
        </div>
    );
}

这里先通过 Btoa 方法将 svg 字符串转成 base64 的url,然后通过 backgroundImage 属性设置成元素的背景图即可。

canvas

这里只放上生成 url 的方法

function generateCanvasUrl(props) {
    const { gapX, gapY, text, width, height, rotate, fontSize, fontFamily, fontWeight,fontStyle } = props;
    const canvas = document.createElement('canvas');
    const ratio = window.devicePixelRatio;
    const ctx = canvas.getContext('2d');

    const canvasWidth = `${(gapX + width) * ratio}px`;
    const canvasHeight = `${(gapY + height) * ratio}px`;

    const markWidth = width * ratio;
    const markHeight = height * ratio;

    canvas.setAttribute('width', canvasWidth);
    canvas.setAttribute('height', canvasHeight);

    if (ctx) {
        ctx.textBaseline = 'middle';
        ctx.textAlign = 'center';
        // 文字绕中间旋转
        ctx.translate(markWidth / 2, markHeight / 2);
        ctx.rotate((Math.PI / 180) * Number(rotate));

        const markSize = Number(fontSize) * ratio;
        ctx.font = `${fontStyle} normal ${fontWeight} ${markSize}px/${markHeight}px ${fontFamily}`;
        ctx.fillStyle = color;
        if (Array.isArray(text)) {
            text.forEach((item: string, index: number) => ctx.fillText(item, 0, index * markSize));
        } else {
            ctx.fillText(text, 0, 0);
        }
        ctx.restore();
        return `url('${canvas.toDataURL()}')`;
    }
}

得到生成后的 url 之后作为 backgroundImage 设置在元素上即可~