前端页面添加水印

4 篇文章 0 订阅

2024新的一年,身体里沉睡的ts觉醒了😪,功能采用typesctipt编写(下有编译好的js)

目录

一、代码

二、使用方法

三、自测

  1. 编译js

  2. 自测

 3. 效果

总结


一、代码

"use strict";

// @ts-check
interface options {
    text: Array<string>
    width: number
    height: number
    rotateDeg: number
    font: string
    fillStyle: string
    opacity: string
    textAlign: CanvasTextAlign
    textBaseline: CanvasTextBaseline
    zIndex: string
}

var uuid = 0;

abstract class Base {
    public dom: HTMLElement;
    public option: options = {
        text: [],
        width: 300,
        height: 100,
        rotateDeg: -25, // 旋转角度
        font: "1.2rem serif", // 字体大小及字体
        fillStyle: "#ccc", // 字体颜色
        opacity: "0.8", // 透明度
        textAlign: "center", // 文本对齐方式 center居中
        textBaseline: "middle", // 文本基线 middle居中
        zIndex: "100000",
    };
    public readonly prefix: string = "WATERMARK-CONTATION";
    public readonly topSpace: number = 25;
    constructor(dom: HTMLElement, option: options) {
        if (dom instanceof Element) {
            this.dom = dom;
        } else {
            throw new Error("argument[0] required elemment");
        }
        if (Object.prototype.toString.call(option) !== "[object Object]") {
            throw new Error("argument[1] required object");
        }
        for (const key in this.option) {
            if (option.hasOwnProperty(key)) {
                this.option[key] = option[key];
            }
        }
    }
    protected getCanvas(): HTMLCanvasElement {
        let canvas = document.createElement("canvas");
        const { rotateDeg, font, fillStyle, textAlign, textBaseline, text, width, height } = this.option;
        canvas.width = width;
        canvas.height = height;
        let ctx = canvas.getContext("2d");
        if (ctx === null) return canvas;
        ctx.rotate(rotateDeg * Math.PI / 180);
        ctx.font = font;
        ctx.fillStyle = fillStyle;
        ctx.textAlign = textAlign;
        ctx.textBaseline = textBaseline;
        if (text && Object.prototype.toString.call(text) === "[object Array]") {
            for (let i = 0; i < text.length; i++) {
                ctx.fillText(text[i], canvas.width / 2, canvas.height + i * this.topSpace);
            }
        }
        return canvas;
    }
    protected getClassName(): string {
        ++uuid;
        return `${this.prefix}-${uuid}`;
    }
    protected getBackground(): string {
        const canvas = this.getCanvas();
        const data = canvas.toDataURL("image/png");
        return `url(${data}) left top repeat`;
    }
}
/**
 * css伪元素实现
 * @param {*} dom 挂载DOM元素
 * @param {*} option 水印参数配置
*/
class CssWatermark extends Base {
    private style: HTMLStyleElement;
    constructor(dom: HTMLElement, option: options) {
        super(dom, option);
    }
    private getStyle(className: string): HTMLStyleElement {
        const background = this.getBackground();
        const { opacity, zIndex } = this.option;
        const style = document.createElement("style");
        style.setAttribute("type", "text/css");
        style.innerHTML = `
            .${className}{ position: relative; }
            .${className}::before {
                content: "";
                position: absolute;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                pointer-events: none;
                background: ${background};
                opacity: ${opacity};
                z-index: ${zIndex};
            }
        `;
        return style;
    }
    public mount(reset: boolean = false): void {
        if (reset) this.destory();
        const className = this.getClassName();
        this.style = this.getStyle(className);
        document.head.appendChild(this.style);
        this.dom.classList.add(className);
    }
    public destory(): void {
        this.style.remove();
        const classList = this.dom.classList;
        const prefix = this.prefix + "-";
        for (let i = 0; i < classList.length; i++) {
            if (classList[i].slice(0, prefix.length) === prefix) {
                classList.remove(classList[i]);
            }
        }
    }
}
/**
 * html元素实现
 * @param {*} dom 挂载DOM元素
 * @param {*} option 水印参数配置
*/
class HtmlWatermark extends Base {
    private mark: HTMLElement;
    private isObserver: Boolean;
    private observerInstance: MutationObserver;
    constructor(dom: HTMLElement, option: options, isObserver: Boolean = false) {
        super(dom, option);
        this.isObserver = isObserver;
    }
    private getElement(className: string): HTMLElement {
        const background = this.getBackground();
        const { opacity, zIndex } = this.option;
        let div = document.createElement("div");
        div.id = className;
        div.style.pointerEvents = "none";
        div.style.top = "0";
        div.style.left = "0";
        div.style.position = "absolute";
        div.style.zIndex = zIndex;
        div.style.width = "100%";
        div.style.height = "100%";
        div.style.opacity = opacity;
        div.style.background = background;
        return div;
    }
    public mount(reset: boolean = false): void {
        if (reset) this.destory();
        const className = this.getClassName();
        this.mark = this.getElement(className);
        this.dom.style.position = "relative";
        this.dom.appendChild(this.mark);
        if (this.isObserver) {
            this.observer();
        }
    }
    public destory(): void {
        this.mark.remove();
        this.dom.style.position = "static";
        if (this.isObserver && this.observerInstance) {
            this.observerInstance.disconnect();
        }
    }
    private observer() {
        const targetNode = document.body;
        const config = {
            attributes: true,
            childList: true,
            subtree: true
        };
        const markStyle = this.mark.getAttribute("style");
        const callback: MutationCallback = (mutationsList: Array<MutationRecord>): void => {
            // Use traditional 'for loops' for IE 11
            for (let mutation of mutationsList) {
                if (mutation.type === "childList" && mutation.removedNodes.length) {
                    if (mutation.removedNodes[0] === this.mark) {
                        this.mount(true)
                    }
                } else if (mutation.type === "attributes" && mutation.target instanceof Element) {
                    if (mutation.target === this.mark) {
                        if (markStyle !== mutation.target.getAttribute("style")) {
                            this.mount(true)
                        }
                    } else if (mutation.target === this.dom) {
                        if (window.getComputedStyle(this.dom).getPropertyValue("position") !== "relative") {
                            this.mount(true);
                        }
                    }
                }
            }
        };
        this.observerInstance = new MutationObserver(callback);
        this.observerInstance.observe(targetNode, config);
    }
}
/**
 * svg 实现
 * @param {*} dom 挂载DOM元素
 * @param {*} option 水印参数配置
*/
class SvgWatermark extends Base {
    private mark: HTMLElement;
    constructor(dom: HTMLElement, option: options) {
        super(dom, option);
    }
    private getElement(className: string): HTMLElement {
        const { zIndex } = this.option;
        let div = document.createElement("div");
        div.id = className;
        div.style.pointerEvents = "none";
        div.style.top = "0";
        div.style.left = "0";
        div.style.position = "fixed";
        div.style.zIndex = zIndex;
        div.style.width = "100%";
        div.style.height = "100%";
        return div;
    }
    private getSvg(): string {
        const { width, height, fillStyle, font, text, rotateDeg, opacity } = this.option;
        const [fontSize, fontFamily] = font.split(" ");
        const content = text.map((item, i) => `
            <text x="${width / 2}" y="${height / 2 + i * this.topSpace}" fill="${fillStyle}" font-size="${fontSize}"
                font-family="${fontFamily}"  text-anchor="middle" dominant-baseline="central"
            >${item}</text>
        `).join("");
        return `
            <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" opacity="${opacity}">
                <defs>
                    <pattern id="watermarkPattern" x="0" y="0" width="${width}" height="${height}" patternUnits="userSpaceOnUse"
                        patternTransform="rotate(${rotateDeg})">
                        ${content}
                    </pattern>
                </defs>
                <rect x="0" y="0" width="100%" height="100%" fill="url(#watermarkPattern)" />
            </svg>
        `
    }
    public mount(reset: boolean = false): void {
        if (reset) this.destory();
        const className = this.getClassName();
        this.mark = this.getElement(className);
        this.mark.innerHTML = this.getSvg();
        this.dom.appendChild(this.mark)
    }
    public destory(): void {
        this.mark.remove();
    }
}

export {
    CssWatermark,
    HtmlWatermark,
    SvgWatermark
}

 代码体量不算大,依赖于ts的特性整体编写下来避免了不少问题。而且代码更易于理解。

"typescript": "5.2.2"

二、使用方法

/**
 * CssWatermark使用方式
 *  let mark = new CssWatermark(document.body, {
            text: ["your name", "1234567890"],
            width: 300,
            height: 150,
            fillStyle: "red",
            font: "20px serif",
        })

        mark.mount()

        // mark.destory() // 销毁

    mount 接受一个boolean,  true => 新建前走销毁 默认false

 * HtmlWatermark使用方式
 *  let mark = new HtmlWatermark(document.body, {
            text: ["your name", "1234567890"],
            width: 300,
            height: 150,
            fillStyle: "red",
            font: "20px serif",
        })

        mark.mount()

        // mark.destory() // 销毁

    mount 接受一个boolean,  true => 新建前走销毁 默认false

    isObserver true 开启监听元素  默认false

 * SvgWatermark使用方式
 *  let mark = new SvgWatermark(document.body, {
            text: ["your name", "1234567890"],
            width: 300,
            height: 150,
            fillStyle: "red",
            font: "20px serif",
        })

        mark.mount()

        // mark.destory() // 销毁

    mount 接受一个boolean,  true => 新建前走销毁 默认false


 */

看着也算的上简单易用吧

三、自测

  1. 编译js

"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SvgWatermark = exports.HtmlWatermark = exports.CssWatermark = void 0;
var uuid = 0;
var Base = /** @class */ (function () {
    function Base(dom, option) {
        this.option = {
            text: [],
            width: 300,
            height: 100,
            rotateDeg: -25,
            font: "1.2rem serif",
            fillStyle: "#ccc",
            opacity: "0.8",
            textAlign: "center",
            textBaseline: "middle",
            zIndex: "100000",
        };
        this.prefix = "WATERMARK-CONTATION";
        this.topSpace = 25;
        if (dom instanceof Element) {
            this.dom = dom;
        }
        else {
            throw new Error("argument[0] required elemment");
        }
        if (Object.prototype.toString.call(option) !== "[object Object]") {
            throw new Error("argument[1] required object");
        }
        for (var key in this.option) {
            if (option.hasOwnProperty(key)) {
                this.option[key] = option[key];
            }
        }
    }
    Base.prototype.getCanvas = function () {
        var canvas = document.createElement("canvas");
        var _a = this.option, rotateDeg = _a.rotateDeg, font = _a.font, fillStyle = _a.fillStyle, textAlign = _a.textAlign, textBaseline = _a.textBaseline, text = _a.text, width = _a.width, height = _a.height;
        canvas.width = width;
        canvas.height = height;
        var ctx = canvas.getContext("2d");
        if (ctx === null)
            return canvas;
        ctx.rotate(rotateDeg * Math.PI / 180);
        ctx.font = font;
        ctx.fillStyle = fillStyle;
        ctx.textAlign = textAlign;
        ctx.textBaseline = textBaseline;
        if (text && Object.prototype.toString.call(text) === "[object Array]") {
            for (var i = 0; i < text.length; i++) {
                ctx.fillText(text[i], canvas.width / 2, canvas.height + i * this.topSpace);
            }
        }
        return canvas;
    };
    Base.prototype.getClassName = function () {
        ++uuid;
        return "".concat(this.prefix, "-").concat(uuid);
    };
    Base.prototype.getBackground = function () {
        var canvas = this.getCanvas();
        var data = canvas.toDataURL("image/png");
        return "url(".concat(data, ") left top repeat");
    };
    return Base;
}());
/**
 * css伪元素实现
 * @param {*} dom 挂载DOM元素
 * @param {*} option 水印参数配置
*/
var CssWatermark = /** @class */ (function (_super) {
    __extends(CssWatermark, _super);
    function CssWatermark(dom, option) {
        return _super.call(this, dom, option) || this;
    }
    CssWatermark.prototype.getStyle = function (className) {
        var background = this.getBackground();
        var _a = this.option, opacity = _a.opacity, zIndex = _a.zIndex;
        var style = document.createElement("style");
        style.setAttribute("type", "text/css");
        style.innerHTML = "\n            .".concat(className, "{ position: relative; }\n            .").concat(className, "::before {\n                content: \"\";\n                position: absolute;\n                top: 0;\n                left: 0;\n                width: 100%;\n                height: 100%;\n                pointer-events: none;\n                background: ").concat(background, ";\n                opacity: ").concat(opacity, ";\n                z-index: ").concat(zIndex, ";\n            }\n        ");
        return style;
    };
    CssWatermark.prototype.mount = function (reset) {
        if (reset === void 0) { reset = false; }
        if (reset)
            this.destory();
        var className = this.getClassName();
        this.style = this.getStyle(className);
        document.head.appendChild(this.style);
        this.dom.classList.add(className);
    };
    CssWatermark.prototype.destory = function () {
        this.style.remove();
        var classList = this.dom.classList;
        var prefix = this.prefix + "-";
        for (var i = 0; i < classList.length; i++) {
            if (classList[i].slice(0, prefix.length) === prefix) {
                classList.remove(classList[i]);
            }
        }
    };
    return CssWatermark;
}(Base));
exports.CssWatermark = CssWatermark;
/**
 * html元素实现
 * @param {*} dom 挂载DOM元素
 * @param {*} option 水印参数配置
*/
var HtmlWatermark = /** @class */ (function (_super) {
    __extends(HtmlWatermark, _super);
    function HtmlWatermark(dom, option, isObserver) {
        if (isObserver === void 0) { isObserver = false; }
        var _this = _super.call(this, dom, option) || this;
        _this.isObserver = isObserver;
        return _this;
    }
    HtmlWatermark.prototype.getElement = function (className) {
        var background = this.getBackground();
        var _a = this.option, opacity = _a.opacity, zIndex = _a.zIndex;
        var div = document.createElement("div");
        div.id = className;
        div.style.pointerEvents = "none";
        div.style.top = "0";
        div.style.left = "0";
        div.style.position = "absolute";
        div.style.zIndex = zIndex;
        div.style.width = "100%";
        div.style.height = "100%";
        div.style.opacity = opacity;
        div.style.background = background;
        return div;
    };
    HtmlWatermark.prototype.mount = function (reset) {
        if (reset === void 0) { reset = false; }
        if (reset)
            this.destory();
        var className = this.getClassName();
        this.mark = this.getElement(className);
        this.dom.style.position = "relative";
        this.dom.appendChild(this.mark);
        if (this.isObserver) {
            this.observer();
        }
    };
    HtmlWatermark.prototype.destory = function () {
        this.mark.remove();
        this.dom.style.position = "static";
        if (this.isObserver && this.observerInstance) {
            this.observerInstance.disconnect();
        }
    };
    HtmlWatermark.prototype.observer = function () {
        var _this = this;
        var targetNode = document.body;
        var config = {
            attributes: true,
            childList: true,
            subtree: true
        };
        var markStyle = this.mark.getAttribute("style");
        var callback = function (mutationsList) {
            // Use traditional 'for loops' for IE 11
            for (var _i = 0, mutationsList_1 = mutationsList; _i < mutationsList_1.length; _i++) {
                var mutation = mutationsList_1[_i];
                if (mutation.type === "childList" && mutation.removedNodes.length) {
                    if (mutation.removedNodes[0] === _this.mark) {
                        _this.mount(true);
                    }
                }
                else if (mutation.type === "attributes" && mutation.target instanceof Element) {
                    if (mutation.target === _this.mark) {
                        if (markStyle !== mutation.target.getAttribute("style")) {
                            _this.mount(true);
                        }
                    }
                    else if (mutation.target === _this.dom) {
                        if (window.getComputedStyle(_this.dom).getPropertyValue("position") !== "relative") {
                            _this.mount(true);
                        }
                    }
                }
            }
        };
        this.observerInstance = new MutationObserver(callback);
        this.observerInstance.observe(targetNode, config);
    };
    return HtmlWatermark;
}(Base));
exports.HtmlWatermark = HtmlWatermark;
/**
 * svg 实现
 * @param {*} dom 挂载DOM元素
 * @param {*} option 水印参数配置
*/
var SvgWatermark = /** @class */ (function (_super) {
    __extends(SvgWatermark, _super);
    function SvgWatermark(dom, option) {
        return _super.call(this, dom, option) || this;
    }
    SvgWatermark.prototype.getElement = function (className) {
        var zIndex = this.option.zIndex;
        var div = document.createElement("div");
        div.id = className;
        div.style.pointerEvents = "none";
        div.style.top = "0";
        div.style.left = "0";
        div.style.position = "fixed";
        div.style.zIndex = zIndex;
        div.style.width = "100%";
        div.style.height = "100%";
        return div;
    };
    SvgWatermark.prototype.getSvg = function () {
        var _this = this;
        var _a = this.option, width = _a.width, height = _a.height, fillStyle = _a.fillStyle, font = _a.font, text = _a.text, rotateDeg = _a.rotateDeg, opacity = _a.opacity;
        var _b = font.split(" "), fontSize = _b[0], fontFamily = _b[1];
        var content = text.map(function (item, i) { return "\n            <text x=\"".concat(width / 2, "\" y=\"").concat(height / 2 + i * _this.topSpace, "\" fill=\"").concat(fillStyle, "\" font-size=\"").concat(fontSize, "\"\n                font-family=\"").concat(fontFamily, "\"  text-anchor=\"middle\" dominant-baseline=\"central\"\n            >").concat(item, "</text>\n        "); }).join("");
        return "\n            <svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"100%\" height=\"100%\" opacity=\"".concat(opacity, "\">\n                <defs>\n                    <pattern id=\"watermarkPattern\" x=\"0\" y=\"0\" width=\"").concat(width, "\" height=\"").concat(height, "\" patternUnits=\"userSpaceOnUse\"\n                        patternTransform=\"rotate(").concat(rotateDeg, ")\">\n                        ").concat(content, "\n                    </pattern>\n                </defs>\n                <rect x=\"0\" y=\"0\" width=\"100%\" height=\"100%\" fill=\"url(#watermarkPattern)\" />\n            </svg>\n        ");
    };
    SvgWatermark.prototype.mount = function (reset) {
        if (reset === void 0) { reset = false; }
        if (reset)
            this.destory();
        var className = this.getClassName();
        this.mark = this.getElement(className);
        this.mark.innerHTML = this.getSvg();
        this.dom.appendChild(this.mark);
    };
    SvgWatermark.prototype.destory = function () {
        this.mark.remove();
    };
    return SvgWatermark;
}(Base));
exports.SvgWatermark = SvgWatermark;

可以修改ts代码,通过下载typescript包执行tsc命令编译js

tsc [文件名]

  2. 自测

<body>
    <div class="contation">
        <div class="css-test"></div>
        <div class="html-test"></div>
    </div>
</body>
let cTest = new CssWatermark(document.querySelector(".css-test"), {
    text: ["css-test", "1234567890"],
    width: 300,
    height: 150,
    fillStyle: "skyblue",
    font: "20px serif",
})

cTest.mount()

let hTest = new HtmlWatermark(document.querySelector(".html-test"), {
    text: ["html-test", "1234567890"],
    width: 300,
    height: 150,
    font: "20px serif",
    opacity: "0.8"
}, true)

hTest.mount()

let sTest = new SvgWatermark(document.body, {
    text: ["svg-test", "1234567890"],
    width: 300,
    height: 150,
    fillStyle: "red",
    font: "20px serif",
})

sTest.mount()

真的不喜欢测试,基本使用应该还是没问题 

 3. 效果

总结

采用了三种方式实现,1.css伪元素通过插入style样式其实有点笨。2.svg的方式是写的比较简单的,只能给全屏,但应该是性能最棒的一种方式。3. 插入html元素应该是比较常见的方式,可以监听修改与删除,但我觉得是个非必要的能力。 实际上懂得利用开发者工具去删改水印的人也都是开发者,身为开发者应该明白水印的意义。

  • 11
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值