@wecode-team/watermark 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -0
- package/dist/index.cjs +164 -0
- package/dist/index.d.cts +57 -0
- package/dist/index.d.ts +57 -0
- package/dist/index.js +137 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# @wecode-team/watermark
|
|
2
|
+
|
|
3
|
+
We0 页面水印组件,支持自定义文字内容、重复平铺、旋转、透明度、字号、颜色和间距配置,适用于后台管理、预览页、演示页等场景。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @wecode-team/watermark
|
|
9
|
+
# 或
|
|
10
|
+
pnpm add @wecode-team/watermark
|
|
11
|
+
# 或
|
|
12
|
+
yarn add @wecode-team/watermark
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
> 需要 React >= 18 作为 peerDependency。
|
|
16
|
+
|
|
17
|
+
## 快速开始
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { We0Watermark } from "@wecode-team/watermark"
|
|
21
|
+
|
|
22
|
+
export default function Page() {
|
|
23
|
+
return (
|
|
24
|
+
<We0Watermark content="WE0 INTERNAL">
|
|
25
|
+
<main style={{ minHeight: 400, padding: 24 }}>
|
|
26
|
+
页面内容
|
|
27
|
+
</main>
|
|
28
|
+
</We0Watermark>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 多行内容
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<We0Watermark
|
|
37
|
+
content={["WE0", "Confidential"]}
|
|
38
|
+
rotate={-18}
|
|
39
|
+
opacity={0.1}
|
|
40
|
+
>
|
|
41
|
+
<section>敏感内容区域</section>
|
|
42
|
+
</We0Watermark>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 自定义样式
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
<We0Watermark
|
|
49
|
+
content="Demo Preview"
|
|
50
|
+
fontSize={18}
|
|
51
|
+
color="#1f2937"
|
|
52
|
+
opacity={0.12}
|
|
53
|
+
gapX={120}
|
|
54
|
+
gapY={96}
|
|
55
|
+
width={200}
|
|
56
|
+
height={120}
|
|
57
|
+
zIndex={2}
|
|
58
|
+
>
|
|
59
|
+
<div>页面内容</div>
|
|
60
|
+
</We0Watermark>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## API
|
|
64
|
+
|
|
65
|
+
### `<We0Watermark />`
|
|
66
|
+
|
|
67
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
68
|
+
|------|------|--------|------|
|
|
69
|
+
| `content` | `string \| string[]` | - | 水印内容,支持单行或多行 |
|
|
70
|
+
| `width` | `number` | `160` | 单个水印绘制区域宽度 |
|
|
71
|
+
| `height` | `number` | `96` | 单个水印绘制区域高度 |
|
|
72
|
+
| `gapX` | `number` | `80` | 水印横向间距 |
|
|
73
|
+
| `gapY` | `number` | `80` | 水印纵向间距 |
|
|
74
|
+
| `rotate` | `number` | `-22` | 水印旋转角度 |
|
|
75
|
+
| `opacity` | `number` | `0.14` | 水印透明度 |
|
|
76
|
+
| `fontSize` | `number` | `16` | 字号 |
|
|
77
|
+
| `lineHeight` | `number` | `1.4` | 多行文字行高倍数 |
|
|
78
|
+
| `color` | `string` | `"#000000"` | 水印颜色 |
|
|
79
|
+
| `fontFamily` | `string` | `"sans-serif"` | 字体族 |
|
|
80
|
+
| `fontWeight` | `CSSProperties["fontWeight"]` | `500` | 字重 |
|
|
81
|
+
| `zIndex` | `number` | `1` | 水印层级 |
|
|
82
|
+
| `enabled` | `boolean` | `true` | 是否启用水印 |
|
|
83
|
+
| `className` | `string` | - | 外层容器类名 |
|
|
84
|
+
| `style` | `CSSProperties` | - | 外层容器样式 |
|
|
85
|
+
| `children` | `ReactNode` | - | 被包裹的页面内容 |
|
|
86
|
+
|
|
87
|
+
## 说明
|
|
88
|
+
|
|
89
|
+
- 水印层默认 `pointer-events: none`,不会阻挡页面交互
|
|
90
|
+
- 组件通过 Canvas 生成背景图并重复平铺
|
|
91
|
+
- 在 SSR 场景下会先渲染内容,客户端挂载后补上水印背景
|
|
92
|
+
- React DevTools 中的组件展示名称为 `We0-Watermark`
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
We0Watermark: () => We0Watermark
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/components/Watermark.tsx
|
|
28
|
+
var import_react = require("react");
|
|
29
|
+
|
|
30
|
+
// src/utils.ts
|
|
31
|
+
var DEFAULT_OPTIONS = {
|
|
32
|
+
width: 160,
|
|
33
|
+
height: 96,
|
|
34
|
+
gapX: 80,
|
|
35
|
+
gapY: 80,
|
|
36
|
+
rotate: -22,
|
|
37
|
+
opacity: 0.14,
|
|
38
|
+
fontSize: 16,
|
|
39
|
+
lineHeight: 1.4,
|
|
40
|
+
color: "#000000",
|
|
41
|
+
fontFamily: "sans-serif",
|
|
42
|
+
fontWeight: 500,
|
|
43
|
+
zIndex: 1,
|
|
44
|
+
enabled: true
|
|
45
|
+
};
|
|
46
|
+
function resolveWatermarkOptions(props) {
|
|
47
|
+
const lines = (Array.isArray(props.content) ? props.content : [props.content]).map((line) => `${line}`.trim()).filter(Boolean);
|
|
48
|
+
return {
|
|
49
|
+
content: lines,
|
|
50
|
+
width: props.width ?? DEFAULT_OPTIONS.width,
|
|
51
|
+
height: props.height ?? DEFAULT_OPTIONS.height,
|
|
52
|
+
gapX: props.gapX ?? DEFAULT_OPTIONS.gapX,
|
|
53
|
+
gapY: props.gapY ?? DEFAULT_OPTIONS.gapY,
|
|
54
|
+
rotate: props.rotate ?? DEFAULT_OPTIONS.rotate,
|
|
55
|
+
opacity: props.opacity ?? DEFAULT_OPTIONS.opacity,
|
|
56
|
+
fontSize: props.fontSize ?? DEFAULT_OPTIONS.fontSize,
|
|
57
|
+
lineHeight: props.lineHeight ?? DEFAULT_OPTIONS.lineHeight,
|
|
58
|
+
color: props.color ?? DEFAULT_OPTIONS.color,
|
|
59
|
+
fontFamily: props.fontFamily ?? DEFAULT_OPTIONS.fontFamily,
|
|
60
|
+
fontWeight: props.fontWeight ?? DEFAULT_OPTIONS.fontWeight,
|
|
61
|
+
zIndex: props.zIndex ?? DEFAULT_OPTIONS.zIndex,
|
|
62
|
+
enabled: props.enabled ?? DEFAULT_OPTIONS.enabled
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function buildFontStyle(fontSize, fontFamily, fontWeight) {
|
|
66
|
+
return `${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
67
|
+
}
|
|
68
|
+
function createWatermarkDataUrl(options) {
|
|
69
|
+
if (typeof document === "undefined") {
|
|
70
|
+
return "";
|
|
71
|
+
}
|
|
72
|
+
const tileWidth = options.width + options.gapX;
|
|
73
|
+
const tileHeight = options.height + options.gapY;
|
|
74
|
+
const canvas = document.createElement("canvas");
|
|
75
|
+
const dpr = window.devicePixelRatio || 1;
|
|
76
|
+
canvas.width = tileWidth * dpr;
|
|
77
|
+
canvas.height = tileHeight * dpr;
|
|
78
|
+
canvas.style.width = `${tileWidth}px`;
|
|
79
|
+
canvas.style.height = `${tileHeight}px`;
|
|
80
|
+
const ctx = canvas.getContext("2d");
|
|
81
|
+
if (!ctx) return "";
|
|
82
|
+
ctx.scale(dpr, dpr);
|
|
83
|
+
ctx.clearRect(0, 0, tileWidth, tileHeight);
|
|
84
|
+
ctx.globalAlpha = options.opacity;
|
|
85
|
+
ctx.fillStyle = options.color;
|
|
86
|
+
ctx.font = buildFontStyle(
|
|
87
|
+
options.fontSize,
|
|
88
|
+
options.fontFamily,
|
|
89
|
+
options.fontWeight
|
|
90
|
+
);
|
|
91
|
+
ctx.textAlign = "center";
|
|
92
|
+
ctx.textBaseline = "middle";
|
|
93
|
+
ctx.translate(tileWidth / 2, tileHeight / 2);
|
|
94
|
+
ctx.rotate(options.rotate * Math.PI / 180);
|
|
95
|
+
const totalTextHeight = options.content.length * options.fontSize * options.lineHeight;
|
|
96
|
+
const startY = -totalTextHeight / 2 + options.fontSize * options.lineHeight / 2;
|
|
97
|
+
options.content.forEach((line, index) => {
|
|
98
|
+
const y = startY + index * options.fontSize * options.lineHeight;
|
|
99
|
+
ctx.fillText(line, 0, y);
|
|
100
|
+
});
|
|
101
|
+
return canvas.toDataURL();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/components/Watermark.tsx
|
|
105
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
106
|
+
function We0Watermark(props) {
|
|
107
|
+
const {
|
|
108
|
+
className,
|
|
109
|
+
style,
|
|
110
|
+
children
|
|
111
|
+
} = props;
|
|
112
|
+
const options = resolveWatermarkOptions(props);
|
|
113
|
+
const contentKey = options.content.join("\n");
|
|
114
|
+
const [dataUrl, setDataUrl] = (0, import_react.useState)("");
|
|
115
|
+
(0, import_react.useEffect)(() => {
|
|
116
|
+
if (!options.enabled || options.content.length === 0) {
|
|
117
|
+
setDataUrl("");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
setDataUrl(createWatermarkDataUrl(options));
|
|
121
|
+
}, [
|
|
122
|
+
options.color,
|
|
123
|
+
contentKey,
|
|
124
|
+
options.enabled,
|
|
125
|
+
options.fontFamily,
|
|
126
|
+
options.fontSize,
|
|
127
|
+
options.fontWeight,
|
|
128
|
+
options.gapX,
|
|
129
|
+
options.gapY,
|
|
130
|
+
options.height,
|
|
131
|
+
options.lineHeight,
|
|
132
|
+
options.opacity,
|
|
133
|
+
options.rotate,
|
|
134
|
+
options.width
|
|
135
|
+
]);
|
|
136
|
+
const overlayStyle = {
|
|
137
|
+
position: "absolute",
|
|
138
|
+
inset: 0,
|
|
139
|
+
pointerEvents: "none",
|
|
140
|
+
zIndex: options.zIndex,
|
|
141
|
+
backgroundImage: dataUrl ? `url("${dataUrl}")` : void 0,
|
|
142
|
+
backgroundRepeat: "repeat",
|
|
143
|
+
backgroundSize: `${options.width + options.gapX}px ${options.height + options.gapY}px`
|
|
144
|
+
};
|
|
145
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
146
|
+
"div",
|
|
147
|
+
{
|
|
148
|
+
className,
|
|
149
|
+
style: {
|
|
150
|
+
position: "relative",
|
|
151
|
+
...style
|
|
152
|
+
},
|
|
153
|
+
children: [
|
|
154
|
+
children,
|
|
155
|
+
options.enabled && options.content.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-hidden": "true", style: overlayStyle }) : null
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
We0Watermark.displayName = "We0-Watermark";
|
|
161
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
162
|
+
0 && (module.exports = {
|
|
163
|
+
We0Watermark
|
|
164
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { CSSProperties, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/** 页面水印组件属性 */
|
|
5
|
+
interface WatermarkProps {
|
|
6
|
+
/** 水印文本,支持单行或多行 */
|
|
7
|
+
content: string | string[];
|
|
8
|
+
/** 单个水印绘制区域宽度,默认 160 */
|
|
9
|
+
width?: number;
|
|
10
|
+
/** 单个水印绘制区域高度,默认 96 */
|
|
11
|
+
height?: number;
|
|
12
|
+
/** 水印之间的横向间距,默认 80 */
|
|
13
|
+
gapX?: number;
|
|
14
|
+
/** 水印之间的纵向间距,默认 80 */
|
|
15
|
+
gapY?: number;
|
|
16
|
+
/** 水印旋转角度,默认 -22 */
|
|
17
|
+
rotate?: number;
|
|
18
|
+
/** 水印透明度,默认 0.14 */
|
|
19
|
+
opacity?: number;
|
|
20
|
+
/** 字号,默认 16 */
|
|
21
|
+
fontSize?: number;
|
|
22
|
+
/** 行高倍数,默认 1.4 */
|
|
23
|
+
lineHeight?: number;
|
|
24
|
+
/** 字体颜色,默认 "#000000" */
|
|
25
|
+
color?: string;
|
|
26
|
+
/** 字体族,默认 "sans-serif" */
|
|
27
|
+
fontFamily?: string;
|
|
28
|
+
/** 字重,默认 500 */
|
|
29
|
+
fontWeight?: CSSProperties["fontWeight"];
|
|
30
|
+
/** 水印层级,默认 1 */
|
|
31
|
+
zIndex?: number;
|
|
32
|
+
/** 是否启用水印,默认 true */
|
|
33
|
+
enabled?: boolean;
|
|
34
|
+
/** 外层容器 className */
|
|
35
|
+
className?: string;
|
|
36
|
+
/** 外层容器样式 */
|
|
37
|
+
style?: CSSProperties;
|
|
38
|
+
/** 子节点内容 */
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 页面水印组件
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* <We0Watermark content="WE0 INTERNAL">
|
|
48
|
+
* <main>页面内容</main>
|
|
49
|
+
* </We0Watermark>
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
declare function We0Watermark(props: WatermarkProps): react_jsx_runtime.JSX.Element;
|
|
53
|
+
declare namespace We0Watermark {
|
|
54
|
+
var displayName: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { type WatermarkProps, We0Watermark };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { CSSProperties, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
/** 页面水印组件属性 */
|
|
5
|
+
interface WatermarkProps {
|
|
6
|
+
/** 水印文本,支持单行或多行 */
|
|
7
|
+
content: string | string[];
|
|
8
|
+
/** 单个水印绘制区域宽度,默认 160 */
|
|
9
|
+
width?: number;
|
|
10
|
+
/** 单个水印绘制区域高度,默认 96 */
|
|
11
|
+
height?: number;
|
|
12
|
+
/** 水印之间的横向间距,默认 80 */
|
|
13
|
+
gapX?: number;
|
|
14
|
+
/** 水印之间的纵向间距,默认 80 */
|
|
15
|
+
gapY?: number;
|
|
16
|
+
/** 水印旋转角度,默认 -22 */
|
|
17
|
+
rotate?: number;
|
|
18
|
+
/** 水印透明度,默认 0.14 */
|
|
19
|
+
opacity?: number;
|
|
20
|
+
/** 字号,默认 16 */
|
|
21
|
+
fontSize?: number;
|
|
22
|
+
/** 行高倍数,默认 1.4 */
|
|
23
|
+
lineHeight?: number;
|
|
24
|
+
/** 字体颜色,默认 "#000000" */
|
|
25
|
+
color?: string;
|
|
26
|
+
/** 字体族,默认 "sans-serif" */
|
|
27
|
+
fontFamily?: string;
|
|
28
|
+
/** 字重,默认 500 */
|
|
29
|
+
fontWeight?: CSSProperties["fontWeight"];
|
|
30
|
+
/** 水印层级,默认 1 */
|
|
31
|
+
zIndex?: number;
|
|
32
|
+
/** 是否启用水印,默认 true */
|
|
33
|
+
enabled?: boolean;
|
|
34
|
+
/** 外层容器 className */
|
|
35
|
+
className?: string;
|
|
36
|
+
/** 外层容器样式 */
|
|
37
|
+
style?: CSSProperties;
|
|
38
|
+
/** 子节点内容 */
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 页面水印组件
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* <We0Watermark content="WE0 INTERNAL">
|
|
48
|
+
* <main>页面内容</main>
|
|
49
|
+
* </We0Watermark>
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
declare function We0Watermark(props: WatermarkProps): react_jsx_runtime.JSX.Element;
|
|
53
|
+
declare namespace We0Watermark {
|
|
54
|
+
var displayName: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export { type WatermarkProps, We0Watermark };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// src/components/Watermark.tsx
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
|
|
4
|
+
// src/utils.ts
|
|
5
|
+
var DEFAULT_OPTIONS = {
|
|
6
|
+
width: 160,
|
|
7
|
+
height: 96,
|
|
8
|
+
gapX: 80,
|
|
9
|
+
gapY: 80,
|
|
10
|
+
rotate: -22,
|
|
11
|
+
opacity: 0.14,
|
|
12
|
+
fontSize: 16,
|
|
13
|
+
lineHeight: 1.4,
|
|
14
|
+
color: "#000000",
|
|
15
|
+
fontFamily: "sans-serif",
|
|
16
|
+
fontWeight: 500,
|
|
17
|
+
zIndex: 1,
|
|
18
|
+
enabled: true
|
|
19
|
+
};
|
|
20
|
+
function resolveWatermarkOptions(props) {
|
|
21
|
+
const lines = (Array.isArray(props.content) ? props.content : [props.content]).map((line) => `${line}`.trim()).filter(Boolean);
|
|
22
|
+
return {
|
|
23
|
+
content: lines,
|
|
24
|
+
width: props.width ?? DEFAULT_OPTIONS.width,
|
|
25
|
+
height: props.height ?? DEFAULT_OPTIONS.height,
|
|
26
|
+
gapX: props.gapX ?? DEFAULT_OPTIONS.gapX,
|
|
27
|
+
gapY: props.gapY ?? DEFAULT_OPTIONS.gapY,
|
|
28
|
+
rotate: props.rotate ?? DEFAULT_OPTIONS.rotate,
|
|
29
|
+
opacity: props.opacity ?? DEFAULT_OPTIONS.opacity,
|
|
30
|
+
fontSize: props.fontSize ?? DEFAULT_OPTIONS.fontSize,
|
|
31
|
+
lineHeight: props.lineHeight ?? DEFAULT_OPTIONS.lineHeight,
|
|
32
|
+
color: props.color ?? DEFAULT_OPTIONS.color,
|
|
33
|
+
fontFamily: props.fontFamily ?? DEFAULT_OPTIONS.fontFamily,
|
|
34
|
+
fontWeight: props.fontWeight ?? DEFAULT_OPTIONS.fontWeight,
|
|
35
|
+
zIndex: props.zIndex ?? DEFAULT_OPTIONS.zIndex,
|
|
36
|
+
enabled: props.enabled ?? DEFAULT_OPTIONS.enabled
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function buildFontStyle(fontSize, fontFamily, fontWeight) {
|
|
40
|
+
return `${fontWeight} ${fontSize}px ${fontFamily}`;
|
|
41
|
+
}
|
|
42
|
+
function createWatermarkDataUrl(options) {
|
|
43
|
+
if (typeof document === "undefined") {
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
const tileWidth = options.width + options.gapX;
|
|
47
|
+
const tileHeight = options.height + options.gapY;
|
|
48
|
+
const canvas = document.createElement("canvas");
|
|
49
|
+
const dpr = window.devicePixelRatio || 1;
|
|
50
|
+
canvas.width = tileWidth * dpr;
|
|
51
|
+
canvas.height = tileHeight * dpr;
|
|
52
|
+
canvas.style.width = `${tileWidth}px`;
|
|
53
|
+
canvas.style.height = `${tileHeight}px`;
|
|
54
|
+
const ctx = canvas.getContext("2d");
|
|
55
|
+
if (!ctx) return "";
|
|
56
|
+
ctx.scale(dpr, dpr);
|
|
57
|
+
ctx.clearRect(0, 0, tileWidth, tileHeight);
|
|
58
|
+
ctx.globalAlpha = options.opacity;
|
|
59
|
+
ctx.fillStyle = options.color;
|
|
60
|
+
ctx.font = buildFontStyle(
|
|
61
|
+
options.fontSize,
|
|
62
|
+
options.fontFamily,
|
|
63
|
+
options.fontWeight
|
|
64
|
+
);
|
|
65
|
+
ctx.textAlign = "center";
|
|
66
|
+
ctx.textBaseline = "middle";
|
|
67
|
+
ctx.translate(tileWidth / 2, tileHeight / 2);
|
|
68
|
+
ctx.rotate(options.rotate * Math.PI / 180);
|
|
69
|
+
const totalTextHeight = options.content.length * options.fontSize * options.lineHeight;
|
|
70
|
+
const startY = -totalTextHeight / 2 + options.fontSize * options.lineHeight / 2;
|
|
71
|
+
options.content.forEach((line, index) => {
|
|
72
|
+
const y = startY + index * options.fontSize * options.lineHeight;
|
|
73
|
+
ctx.fillText(line, 0, y);
|
|
74
|
+
});
|
|
75
|
+
return canvas.toDataURL();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/components/Watermark.tsx
|
|
79
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
80
|
+
function We0Watermark(props) {
|
|
81
|
+
const {
|
|
82
|
+
className,
|
|
83
|
+
style,
|
|
84
|
+
children
|
|
85
|
+
} = props;
|
|
86
|
+
const options = resolveWatermarkOptions(props);
|
|
87
|
+
const contentKey = options.content.join("\n");
|
|
88
|
+
const [dataUrl, setDataUrl] = useState("");
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (!options.enabled || options.content.length === 0) {
|
|
91
|
+
setDataUrl("");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
setDataUrl(createWatermarkDataUrl(options));
|
|
95
|
+
}, [
|
|
96
|
+
options.color,
|
|
97
|
+
contentKey,
|
|
98
|
+
options.enabled,
|
|
99
|
+
options.fontFamily,
|
|
100
|
+
options.fontSize,
|
|
101
|
+
options.fontWeight,
|
|
102
|
+
options.gapX,
|
|
103
|
+
options.gapY,
|
|
104
|
+
options.height,
|
|
105
|
+
options.lineHeight,
|
|
106
|
+
options.opacity,
|
|
107
|
+
options.rotate,
|
|
108
|
+
options.width
|
|
109
|
+
]);
|
|
110
|
+
const overlayStyle = {
|
|
111
|
+
position: "absolute",
|
|
112
|
+
inset: 0,
|
|
113
|
+
pointerEvents: "none",
|
|
114
|
+
zIndex: options.zIndex,
|
|
115
|
+
backgroundImage: dataUrl ? `url("${dataUrl}")` : void 0,
|
|
116
|
+
backgroundRepeat: "repeat",
|
|
117
|
+
backgroundSize: `${options.width + options.gapX}px ${options.height + options.gapY}px`
|
|
118
|
+
};
|
|
119
|
+
return /* @__PURE__ */ jsxs(
|
|
120
|
+
"div",
|
|
121
|
+
{
|
|
122
|
+
className,
|
|
123
|
+
style: {
|
|
124
|
+
position: "relative",
|
|
125
|
+
...style
|
|
126
|
+
},
|
|
127
|
+
children: [
|
|
128
|
+
children,
|
|
129
|
+
options.enabled && options.content.length > 0 ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", style: overlayStyle }) : null
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
We0Watermark.displayName = "We0-Watermark";
|
|
135
|
+
export {
|
|
136
|
+
We0Watermark
|
|
137
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wecode-team/watermark",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "We0 页面水印组件 —— 支持自定义文字内容、重复平铺和样式配置",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"dev": "tsup --watch",
|
|
31
|
+
"prepublishOnly": "npm run build"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": ">=18.0.0",
|
|
35
|
+
"react-dom": ">=18.0.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/react": "^19.0.0",
|
|
39
|
+
"react": "^19.0.0",
|
|
40
|
+
"tsup": "^8.4.0",
|
|
41
|
+
"typescript": "^5.9.2"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"watermark",
|
|
45
|
+
"overlay",
|
|
46
|
+
"react",
|
|
47
|
+
"text-watermark",
|
|
48
|
+
"we0"
|
|
49
|
+
],
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"license": "MIT",
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "https://github.com/we0-org/we0-sdk.git",
|
|
57
|
+
"directory": "packages/watermark"
|
|
58
|
+
},
|
|
59
|
+
"homepage": "https://github.com/we0-org/we0-sdk/tree/main/packages/watermark#readme",
|
|
60
|
+
"bugs": {
|
|
61
|
+
"url": "https://github.com/we0-org/we0-sdk/issues"
|
|
62
|
+
}
|
|
63
|
+
}
|