2024新的一年,身体里沉睡的ts觉醒了😪,功能采用typesctipt编写(下有编译好的js)
目录
一、代码
"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元素应该是比较常见的方式,可以监听修改与删除,但我觉得是个非必要的能力。 实际上懂得利用开发者工具去删改水印的人也都是开发者,身为开发者应该明白水印的意义。