Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(function): add watermark custom style #3634

Merged
merged 1 commit into from
Mar 2, 2024
Merged
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
66 changes: 53 additions & 13 deletions src/hooks/web/useWatermark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,65 @@ type UseWatermarkRes = {
setWatermark: (str: string) => void;
clear: () => void;
clearAll: () => void;
waterMarkOptions?: waterMarkOptionsType;
obInstance?: MutationObserver;
targetElement?: HTMLElement;
parentElement?: HTMLElement;
};

type waterMarkOptionsType = {
// 自定义水印的文字大小
fontSize?: number;
// 自定义水印的文字颜色
fontColor?: string;
// 自定义水印的文字字体
fontFamily?: string;
// 自定义水印的文字对齐方式
textAlign?: CanvasTextAlign;
// 自定义水印的文字基线
textBaseline?: CanvasTextBaseline;
// 自定义水印的文字倾斜角度
rotate?: number;
};

const sourceMap = new Map<Symbol, Omit<UseWatermarkRes, 'clearAll'>>();

function createBase64(str: string) {
function findTargetNode(el) {
return Array.from(sourceMap.values()).find((item) => item.targetElement === el);
}

function createBase64(str: string, waterMarkOptions: waterMarkOptionsType) {
const can = document.createElement('canvas');
const width = 300;
const height = 240;
Object.assign(can, { width, height });

const cans = can.getContext('2d');
if (cans) {
cans.rotate((-20 * Math.PI) / 180);
cans.font = '15px Vedana';
cans.fillStyle = 'rgba(0, 0, 0, 0.15)';
cans.textAlign = 'left';
cans.textBaseline = 'middle';
const fontFamily = waterMarkOptions?.fontFamily || 'Vedana';
const fontSize = waterMarkOptions?.fontSize || 15;
const fontColor = waterMarkOptions?.fontColor || 'rgba(0, 0, 0, 0.15)';
const textAlign = waterMarkOptions?.textAlign || 'left';
const textBaseline = waterMarkOptions?.textBaseline || 'middle';
const rotate = waterMarkOptions?.rotate || 20;
cans.rotate((-rotate * Math.PI) / 180);
cans.font = `${fontSize}px ${fontFamily}`;
cans.fillStyle = fontColor;
cans.textAlign = textAlign;
cans.textBaseline = textBaseline;
cans.fillText(str, width / 20, height);
// todo 自定义水印样式
}
return can.toDataURL('image/png');
}
const resetWatermarkStyle = (element: HTMLElement, watermarkText: string) => {
const resetWatermarkStyle = (
element: HTMLElement,
watermarkText: string,
waterMarkOptions: waterMarkOptionsType,
) => {
element.className = '__' + watermarkSymbol;
element.style.pointerEvents = 'none';
element.style.display = 'block';
element.style.visibility = 'visible';
element.style.top = '0px';
element.style.left = '0px';
element.style.position = 'absolute';
Expand All @@ -46,14 +77,15 @@ const resetWatermarkStyle = (element: HTMLElement, watermarkText: string) => {
element.style.width = '100%';
element.style.background = `url(${createBase64(
unref(updateWatermarkText) || watermarkText,
waterMarkOptions,
)}) left top repeat`;
};

const obFn = () => {
const obInstance = new MutationObserver((mutationRecords) => {
for (const mutation of mutationRecords) {
for (const node of Array.from(mutation.removedNodes)) {
const target = Array.from(sourceMap.values()).find((item) => item.targetElement === node);
const target = findTargetNode(node);
if (!target) return;
const { targetElement, parentElement } = target;
// 父元素的子元素水印如果被删除 重新插入被删除的水印(防篡改,插入通过控制台删除的水印)
Expand All @@ -63,8 +95,14 @@ const obFn = () => {
}
if (mutation.attributeName === 'style' && mutation.target) {
const _target = mutation.target as HTMLElement;
if (_target.className === '__' + watermarkSymbol) {
resetWatermarkStyle(_target as HTMLElement, _target?.['data-watermark-text']);
const target = findTargetNode(_target);
if (target) {
const { waterMarkOptions = {} } = target;
resetWatermarkStyle(
_target as HTMLElement,
_target?.['data-watermark-text'],
waterMarkOptions,
);
}
}
}
Expand All @@ -74,6 +112,7 @@ const obFn = () => {

export function useWatermark(
appendEl: Ref<HTMLElement | null> = ref(document.body) as Ref<HTMLElement>,
waterMarkOptions: waterMarkOptionsType = {},
): UseWatermarkRes {
const domSymbol = Symbol(watermarkSymbol);
const appendElRaw = unref(appendEl);
Expand Down Expand Up @@ -115,7 +154,7 @@ export function useWatermark(
el.style.height = `${options.height}px`;
}
if (isDef(options.str)) {
el.style.background = `url(${createBase64(options.str)}) left top repeat`;
el.style.background = `url(${createBase64(options.str, waterMarkOptions)}) left top repeat`;
}
}

Expand All @@ -129,7 +168,7 @@ export function useWatermark(
div['data-watermark-text'] = str; //自定义属性 用于恢复水印
updateWatermarkText.value = str;
watermarkEl.value = div;
resetWatermarkStyle(div, str);
resetWatermarkStyle(div, str, waterMarkOptions);
const el = unref(appendEl);
if (!el) return;
const { clientHeight: height, clientWidth: width } = el;
Expand All @@ -141,6 +180,7 @@ export function useWatermark(
parentElement: el,
targetElement: div,
obInstance: obFn(),
waterMarkOptions,
});
sourceMap.get(domSymbol)?.obInstance?.observe(el, {
childList: true, // 子节点的变动(指新增,删除或者更改)
Expand Down
12 changes: 11 additions & 1 deletion src/views/demo/feat/watermark/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
<a-button type="primary" class="mr-2" @click="setWatermark2('WaterMark Info2')">
Create Watermark2
</a-button>
<a-button type="primary" class="mr-2" @click="setWatermark3('Custome Style WaterMark')">
Create custom style Watermark
</a-button>
<a-button color="error" class="mr-2" @click="clear"> Clear Watermark1 </a-button>
<a-button color="error" class="mr-2" @click="clearAll"> ClearAll </a-button>
<a-button color="warning" class="mr-2" @click="setWatermark('WaterMark Info New')">
Expand All @@ -16,13 +19,20 @@
</PageWrapper>
</template>
<script lang="ts" setup>
import { onUnmounted } from 'vue';
import { onUnmounted, ref } from 'vue';
import { CollapseContainer } from '@/components/Container';
import { useWatermark } from '@/hooks/web/useWatermark';
import { PageWrapper } from '@/components/Page';

const app = ref(document.body);

const { setWatermark, clear, clearAll } = useWatermark();
const { setWatermark: setWatermark2 } = useWatermark();
const { setWatermark: setWatermark3 } = useWatermark(app, {
fontColor: 'red',
fontSize:12,
rotate:30
});

onUnmounted(() => {
clearAll();
Expand Down
Loading