Skip to content

Commit 6058ca5

Browse files
authored
feat: add watermark (#6300)
* feat: add watermark * feat: add watermark demo * feat: add mutationObserver * feat: add watermark demo
1 parent dd063b8 commit 6058ca5

File tree

14 files changed

+710
-1
lines changed

14 files changed

+710
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { tryOnScopeDispose } from './tryOnScopeDispose';
2+
import { watch } from 'vue';
3+
import type { MaybeElementRef } from './unrefElement';
4+
import { unrefElement } from './unrefElement';
5+
import { useSupported } from './useSupported';
6+
import type { ConfigurableWindow } from './_configurable';
7+
import { defaultWindow } from './_configurable';
8+
9+
export interface UseMutationObserverOptions extends MutationObserverInit, ConfigurableWindow {}
10+
11+
/**
12+
* Watch for changes being made to the DOM tree.
13+
*
14+
* @see https://vueuse.org/useMutationObserver
15+
* @see https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver MutationObserver MDN
16+
* @param target
17+
* @param callback
18+
* @param options
19+
*/
20+
export function useMutationObserver(
21+
target: MaybeElementRef,
22+
callback: MutationCallback,
23+
options: UseMutationObserverOptions = {},
24+
) {
25+
const { window = defaultWindow, ...mutationOptions } = options;
26+
let observer: MutationObserver | undefined;
27+
const isSupported = useSupported(() => window && 'MutationObserver' in window);
28+
29+
const cleanup = () => {
30+
if (observer) {
31+
observer.disconnect();
32+
observer = undefined;
33+
}
34+
};
35+
36+
const stopWatch = watch(
37+
() => unrefElement(target),
38+
el => {
39+
cleanup();
40+
41+
if (isSupported.value && window && el) {
42+
observer = new MutationObserver(callback);
43+
observer!.observe(el, mutationOptions);
44+
}
45+
},
46+
{ immediate: true },
47+
);
48+
49+
const stop = () => {
50+
cleanup();
51+
stopWatch();
52+
};
53+
54+
tryOnScopeDispose(stop);
55+
56+
return {
57+
isSupported,
58+
stop,
59+
};
60+
}
61+
62+
export type UseMutationObserverReturn = ReturnType<typeof useMutationObserver>;

components/components.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ export { default as Upload, UploadDragger } from './upload';
245245

246246
export { default as LocaleProvider } from './locale-provider';
247247

248-
export type { SegmentedProps } from './segmented';
248+
export { default as Watermark } from './watermark';
249+
export type { WatermarkProps } from './watermark';
249250

251+
export type { SegmentedProps } from './segmented';
250252
export { default as Segmented } from './segmented';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import demoTest from '../../../tests/shared/demoTest';
2+
3+
demoTest('watermark');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Watermark from '..';
2+
import mountTest from '../../../tests/shared/mountTest';
3+
import { mount } from '@vue/test-utils';
4+
5+
describe('Watermark', () => {
6+
mountTest(Watermark);
7+
const mockSrcSet = jest.spyOn(Image.prototype, 'src', 'set');
8+
beforeAll(() => {
9+
mockSrcSet.mockImplementation(function fn() {
10+
this.onload?.();
11+
});
12+
});
13+
14+
afterAll(() => {
15+
mockSrcSet.mockRestore();
16+
});
17+
18+
it('The watermark should render successfully ', function () {
19+
const wrapper = mount({
20+
setup() {
21+
return () => {
22+
return <Watermark class="watermark" content="Ant Design" />;
23+
};
24+
},
25+
});
26+
expect(wrapper.find('.watermark').exists()).toBe(true);
27+
expect(wrapper.html()).toMatchSnapshot();
28+
});
29+
});

components/watermark/demo/basic.vue

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<docs>
2+
---
3+
order: 0
4+
title:
5+
zh-CN: 基本
6+
en-US: Basic
7+
---
8+
9+
## zh-CN
10+
11+
最简单的用法。
12+
13+
## en-US
14+
15+
The most basic usage.
16+
17+
</docs>
18+
19+
<template>
20+
<a-watermark content="Ant Design Vue">
21+
<div style="height: 500px" />
22+
</a-watermark>
23+
</template>

components/watermark/demo/custom.vue

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<docs>
2+
---
3+
order: 0
4+
title:
5+
zh-CN: 自定义配置
6+
en-US: Custom
7+
---
8+
9+
## zh-CN
10+
11+
通过自定义参数配置预览水印效果。
12+
13+
## en-US
14+
15+
Preview the watermark effect by configuring custom parameters.
16+
17+
</docs>
18+
19+
<template>
20+
<div style="display: flex">
21+
<a-watermark v-bind="model">
22+
<a-typography>
23+
<a-typography-paragraph>
24+
The light-speed iteration of the digital world makes products more complex. However, human
25+
consciousness and attention resources are limited. Facing this design contradiction, the
26+
pursuit of natural interaction will be the consistent direction of Ant Design.
27+
</a-typography-paragraph>
28+
<a-typography-paragraph>
29+
Natural user cognition: According to cognitive psychology, about 80% of external
30+
information is obtained through visual channels. The most important visual elements in the
31+
interface design, including layout, colors, illustrations, icons, etc., should fully
32+
absorb the laws of nature, thereby reducing the user&apos;s cognitive cost and bringing
33+
authentic and smooth feelings. In some scenarios, opportunely adding other sensory
34+
channels such as hearing, touch can create a richer and more natural product experience.
35+
</a-typography-paragraph>
36+
<a-typography-paragraph>
37+
Natural user behavior: In the interaction with the system, the designer should fully
38+
understand the relationship between users, system roles, and task objectives, and also
39+
contextually organize system functions and services. At the same time, a series of methods
40+
such as behavior analysis, artificial intelligence and sensors could be applied to assist
41+
users to make effective decisions and reduce extra operations of users, to save
42+
users&apos; mental and physical resources and make human-computer interaction more
43+
natural.
44+
</a-typography-paragraph>
45+
</a-typography>
46+
<img
47+
style="z-index: 10; width: 100%; max-width: 800px; position: relative"
48+
src="https://gw.alipayobjects.com/mdn/rms_08e378/afts/img/A*zx7LTI_ECSAAAAAAAAAAAABkARQnAQ"
49+
alt="示例图片"
50+
/>
51+
</a-watermark>
52+
<a-form
53+
style="
54+
width: 280px;
55+
flex-shrink: 0;
56+
border-left: 1px solid #eee;
57+
padding-left: 20px;
58+
margin-left: 20px;
59+
"
60+
layout="vertical"
61+
:model="model"
62+
>
63+
<a-form-item name="content" label="Content">
64+
<a-input v-model:value="model.content" />
65+
</a-form-item>
66+
<a-form-item name="font.fontSize" label="FontSize">
67+
<a-slider v-model:value="model.font.fontSize" :step="1" :min="0" :max="100" />
68+
</a-form-item>
69+
<a-form-item name="zIndex" label="zIndex">
70+
<a-slider v-model:value="model.zIndex" :step="1" :min="0" :max="100" />
71+
</a-form-item>
72+
<a-form-item name="rotate" label="Rotate">
73+
<a-slider v-model:value="model.rotate" :step="1" :min="-180" :max="180" />
74+
</a-form-item>
75+
<a-form-item label="Gap" style="margin-bottom: 0">
76+
<a-space style="display: flex" align="baseline">
77+
<a-form-item :name="['gap', 0]">
78+
<a-input-number v-model:value="model.gap[0]" placeholder="gapX" />
79+
</a-form-item>
80+
<a-form-item :name="['gap', 1]">
81+
<a-input-number v-model:value="model.gap[1]" placeholder="gapY" />
82+
</a-form-item>
83+
</a-space>
84+
</a-form-item>
85+
<a-form-item label="Offset" style="margin-bottom: 0">
86+
<a-space style="display: flex" align="baseline">
87+
<a-form-item :name="['offset', 0]">
88+
<a-input-number v-model:value="model.offset[0]" placeholder="offsetLeft" />
89+
</a-form-item>
90+
<a-form-item :name="['offset', 1]">
91+
<a-input-number v-model:value="model.offset[1]" placeholder="offsetTop" />
92+
</a-form-item>
93+
</a-space>
94+
</a-form-item>
95+
</a-form>
96+
</div>
97+
</template>
98+
99+
<script lang="ts">
100+
import { defineComponent, reactive } from 'vue';
101+
export default defineComponent({
102+
setup() {
103+
const model = reactive({
104+
content: 'Ant Design Vue',
105+
font: {
106+
fontSize: 16,
107+
},
108+
zIndex: 11,
109+
rotate: -22,
110+
gap: [100, 100] as [number, number],
111+
offset: [],
112+
});
113+
return {
114+
model,
115+
};
116+
},
117+
});
118+
</script>

components/watermark/demo/image.vue

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<docs>
2+
---
3+
order: 0
4+
title:
5+
zh-CN: 图片水印
6+
en-US: Image watermark
7+
---
8+
9+
## zh-CN
10+
11+
通过 `image` 指定图片地址。为保证图片高清且不被拉伸,请设置 width 和 height, 并上传至少两倍的宽高的 logo 图片地址。
12+
13+
## en-US
14+
15+
Specify the image address via 'image'. To ensure that the image is high definition and not stretched, set the width and height, and upload at least twice the width and height of the logo image address.
16+
17+
</docs>
18+
19+
<template>
20+
<a-watermark
21+
:height="30"
22+
:width="130"
23+
image="https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*lkAoRbywo0oAAAAAAAAAAAAADrJ8AQ/original"
24+
>
25+
<div style="height: 500px" />
26+
</a-watermark>
27+
</template>

components/watermark/demo/index.vue

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<template>
2+
<demo-sort :cols="1">
3+
<basic />
4+
<multi-line />
5+
<watermark-image />
6+
<custom />
7+
</demo-sort>
8+
</template>
9+
10+
<script lang="ts">
11+
import { defineComponent } from 'vue';
12+
import CN from '../index.zh-CN.md';
13+
import US from '../index.en-US.md';
14+
import Basic from './basic.vue';
15+
import MultiLine from './multi-line.vue';
16+
import WatermarkImage from './image.vue';
17+
import Custom from './custom.vue';
18+
export default defineComponent({
19+
CN,
20+
US,
21+
components: {
22+
Basic,
23+
MultiLine,
24+
WatermarkImage,
25+
Custom,
26+
},
27+
});
28+
</script>
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<docs>
2+
---
3+
order: 0
4+
title:
5+
zh-CN: 多行水印
6+
en-US: Multi-line watermark
7+
---
8+
9+
## zh-CN
10+
11+
通过 `content` 设置 字符串数组 指定多行文字水印内容。
12+
13+
## en-US
14+
15+
Use 'content' to set a string array to specify multi-line text watermark content.
16+
17+
</docs>
18+
19+
<template>
20+
<a-watermark :content="['Ant Design Vue', 'Happy Working']">
21+
<div style="height: 500px" />
22+
</a-watermark>
23+
</template>

components/watermark/index.en-US.md

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
category: Components
3+
type: Other
4+
title: Watermark
5+
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*wr1ISY50SyYAAAAAAAAAAAAADrJ8AQ/original
6+
coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*duAQQbjHlHQAAAAAAAAAAAAADrJ8AQ/original
7+
---
8+
9+
Add specific text or patterns to the page.
10+
11+
## When To Use
12+
13+
- Use when the page needs to be watermarked to identify the copyright.
14+
- Suitable for preventing information theft.
15+
16+
## API
17+
18+
### Watermark
19+
20+
| Property | Description | Type | Default | Version |
21+
| --- | --- | --- | --- | --- |
22+
| width | The width of the watermark, the default value of `content` is its own width | number | 120 | |
23+
| height | The height of the watermark, the default value of `content` is its own height | number | 64 | |
24+
| rotate | When the watermark is drawn, the rotation Angle, unit `°` | number | -22 | |
25+
| zIndex | The z-index of the appended watermark element | number | 9 | |
26+
| image | Image source, it is recommended to export 2x or 3x image, high priority | string | - | |
27+
| content | Watermark text content | string \| string[] | - | |
28+
| font | Text style | [Font](#font) | [Font](#font) | |
29+
| gap | The spacing between watermarks | \[number, number\] | \[100, 100\] | |
30+
| offset | The offset of the watermark from the upper left corner of the container. The default is `gap/2` | \[number, number\] | \[gap\[0\]/2, gap\[1\]/2\] | |
31+
32+
### Font
33+
34+
<!-- prettier-ignore -->
35+
| Property | Description | Type | Default | Version |
36+
| --- | --- | --- | --- | --- |
37+
| color | font color | string | rgba(0,0,0,.15) | |
38+
| fontSize | font size | number | 16 | |
39+
| fontWeight | font weight | `normal` \| `light` \| `weight` \| number | normal | |
40+
| fontFamily | font family | string | sans-serif | |
41+
| fontStyle | font style | `none` \| `normal` \| `italic` \| `oblique` | normal | |

0 commit comments

Comments
 (0)