Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/rc-ui-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
"@types/react-transition-group": "^4.4.5",
"gh-pages": "^3.2.3",
"react": "^17.0.2",
"react-dom": "^17.0.2"
"react-dom": "^17.0.2",
"jest-canvas-mock": "2.5.2"
},
"browserslist": [
"Android >= 4.0",
Expand Down
30 changes: 30 additions & 0 deletions packages/rc-ui-lib/src/watermark/PropsType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { BaseTypeProps } from '../utils';

export interface WatermarkProps extends BaseTypeProps {
/** 水印宽度, 默认值为100 */
width?: number;
/** 水印高度, 默认值为100 */
height?: number;
/** 水印的 z-index, 默认值为100 */
zIndex?: number | string;
/** 文字水印的内容 */
content?: string;
/** 图片水印的内容,如果与 content 同时传入,优先使用图片水印 */
image?: string;
/** 水印的旋转角度 默认-22 */
rotate?: number | string;
/** 水印是否全屏显示 */
fullPage?: boolean;
/** 水印之间的水平间隔 */
gapX?: number;
/** 水印之间的垂直间隔 */
gapY?: number;
/** 文字水印的颜色 */
textColor?: string;
/** 水印的透明度 */
opacity?: number;
/**
* 子元素
*/
children?: React.ReactNode;
}
105 changes: 105 additions & 0 deletions packages/rc-ui-lib/src/watermark/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# WaterMark 水印

### 介绍

给页面的某个区域加上水印,支持文字和图案。请升级 rc-ui-lib 到 >= 2.1.0 版本来使用该组件。

### 引入

适用于防止信息盗用、标识版权时使用。

```js
import { Watermark } from 'rc-ui-lib';
```

## 代码演示

### 文字水印

通过 `content` 属性来设置水印的文字。

```jsx
<Watermark content="rc-ui-lib" />
```

### 图片水印

通过 `image` 属性来设置水印图片,并使用 `opacity` 来调整水印的整体透明度。

```jsx
<Watermark
image="https://rancui.github.io/rc-ui-lib/rc-ui-lib.png"
width={180}
height={90}
opacity={0.2}
/>
```

### 自定义间隔

通过 `gapX` 和 `gapY` 属性来控制多个重复水印之间的间隔。

```jsx
<Watermark
image="https://rancui.github.io/rc-ui-lib/rc-ui-lib.png"
gapX={30}
gapY={10}
opacity={0.2}
/>
```

### 自定义倾斜角度

通过 `rotate` 属性来控制水印的倾斜角度,默认值为`-22`。

```jsx
<Watermark image="https://rancui.github.io/rc-ui-lib/rc-ui-lib.png" rotate="22" opacity={0.2} />
```

### 显示范围

通过 `full-page` 属性来控制水印的显示范围。

```jsx
<Watermark image="https://rancui.github.io/rc-ui-lib/rc-ui-lib.png" fullPage />
```

### HTML 水印

通过 `children` 可以直接传入 HTML 作为水印。HTML 中的样式仅支持行内样式,同时不支持传入自闭合标签。

```jsx
<Watermark width={150}>
<div style={{ background: 'linear-gradient(45deg, #000 0, #000 50%, #fff 50%)' }}>
<p style={{ mixBlendMode: 'difference', color: '#fff' }}>rc watermark</p>
</div>
</Watermark>
```

## API

### Props

| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| width | 水印宽度 | _number_ | `100` |
| height | 水印高度 | _number_ | `100` |
| z-index | 水印的 z-index | _number \| string_ | `100` |
| content | 文字水印的内容 | _string_ | - |
| image | 图片水印的内容,如果与 `content` 同时传入,优先使用图片水印 | _string_ | - |
| rotate | 水印的旋转角度 | _number \| string_ | `-22` |
| full-page | 水印是否全屏显示 | _boolean_ | `false` |
| gap-x | 水印之间的水平间隔 | _number_ | `0` |
| gap-y | 水印之间的垂直间隔 | _number_ | `0` |
| text-color | 文字水印的颜色 | _string_ | `#dcdee0` |
| opacity | 水印的透明度 | _number_ | - |

## 主题定制

### 样式变量

组件提供了下列 CSS 变量,可用于自定义样式,使用方法请参考 [ConfigProvider 组件](/components/config-provider)。

| 名称 | 默认值 | 描述 |
| ----------------------- | ------ | ---- |
| --rc-water-mark-z-index | _100_ | - |
151 changes: 151 additions & 0 deletions packages/rc-ui-lib/src/watermark/Watermark.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import React, { useContext, useEffect, useRef, useState } from 'react';
import classnames from 'classnames';
import { WatermarkProps } from './PropsType';
import ConfigProviderContext from '../config-provider/ConfigProviderContext';

const Watermark: React.FC<WatermarkProps> = (props) => {
const { prefixCls, createNamespace } = useContext(ConfigProviderContext);
const [bem] = createNamespace('water-mark', prefixCls);
const {
width,
height,
zIndex,
content,
image,
rotate,
fullPage,
gapX,
gapY,
textColor,
opacity,
children,
} = props;
const [imageBase64, setImageBase64] = useState('');
const svgElRef = useRef<HTMLDivElement>();
const [watermarkUrl, setWatermarkUrl] = useState('');
const renderWatermark = () => {
const rotateStyle = {
transformOrigin: 'center',
transform: `rotate(${rotate}deg)`,
};

const svgInner = () => {
if (image && !children) {
return (
<image
href={imageBase64}
// Compatite for versions below Safari 12
// More detail: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/xlink:href
// @ts-ignore
xlinkHref={imageBase64}
x="0"
y="0"
width={width}
height={height}
style={rotateStyle}
/>
);
}

return (
<foreignObject x="0" y="0" width={width} height={height}>
<div
// @ts-ignore
xmlns="http://www.w3.org/1999/xhtml"
style={rotateStyle}
>
{children || <span style={{ color: textColor }}>{content}</span>}
</div>
</foreignObject>
);
};

const svgWidth = width + gapX;
const svgHeight = height + gapY;

return (
<svg
viewBox={`0 0 ${svgWidth} ${svgHeight}`}
width={svgWidth}
height={svgHeight}
xmlns="http://www.w3.org/2000/svg"
// xlink namespace for compatite image xlink attribute
// @ts-ignore
xmlnsXlink="http://www.w3.org/1999/xlink"
style={{
padding: `0 ${gapX}px ${gapY}px 0`,
opacity: opacity,
display: 'none',
}}
>
{svgInner()}
</svg>
);
};

const makeImageToBase64 = (url: string) => {
const canvas = document.createElement('canvas');
const image = new Image();
image.crossOrigin = 'anonymous';
image.referrerPolicy = 'no-referrer';
image.onload = () => {
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
const ctx = canvas.getContext('2d');
ctx?.drawImage(image, 0, 0);
setImageBase64(canvas.toDataURL());
};
image.src = url;
};
const makeSvgToBlobUrl = (svgStr: string) => {
// svg MIME type: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
const svgBlob = new Blob([svgStr], {
type: 'image/svg+xml',
});
return URL.createObjectURL(svgBlob);
};

useEffect(() => {
if (svgElRef.current) {
if (watermarkUrl) {
URL.revokeObjectURL(watermarkUrl);
}
setWatermarkUrl(makeSvgToBlobUrl(svgElRef.current.innerHTML));
}
}, [imageBase64, gapX, gapY, content, textColor, width, height, rotate]);
useEffect(() => {
if (image) makeImageToBase64(image);
}, [image]);
useEffect(() => {
return () => {
if (watermarkUrl) {
URL.revokeObjectURL(watermarkUrl);
}
};
}, []);
return (
<div
className={classnames(bem({ full: fullPage }))}
style={{
zIndex,
backgroundImage: `url(${watermarkUrl})`,
backgroundSize: `${gapX + width}px ${gapY + height}px `,
}}
>
<div ref={svgElRef}>{renderWatermark()}</div>
</div>
);
};

Watermark.defaultProps = {
width: 100,
height: 100,
zIndex: 100,
rotate: -22,
fullPage: false,
gapX: 0,
gapY: 0,
textColor: '#dcdee0',
};

export default Watermark;
Loading