This repository has been archived by the owner on Aug 28, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 156
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* feat(vbenComponents): Watermark 添加水印组件 * feat(hooks): add useWatermark * feat(demo): add Watermark page(水印-demo #181) * fix(hooks): 修复useWatermark属性更改时恢复水印 * fix(hooks): 修复useWatermark 自定义属性问题 --------- Co-authored-by: jackhoo_98 <jackhoo_98@foxmail.com>
- Loading branch information
1 parent
7ddab2b
commit e28d304
Showing
8 changed files
with
334 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
import { | ||
getCurrentInstance, | ||
onBeforeUnmount, | ||
ref, | ||
Ref, | ||
shallowRef, | ||
unref, | ||
} from 'vue' | ||
import { | ||
addResizeListener, | ||
removeResizeListener, | ||
useThrottleFn, | ||
isUndefined, | ||
} from '@vben/utils' | ||
|
||
const watermarkSymbol = 'watermark-dom' | ||
const updateWatermarkText = ref<string | null>(null) | ||
|
||
type UseWatermarkRes = { | ||
setWatermark: (str: string) => void | ||
clear: () => void | ||
clearAll: () => void | ||
obInstance?: MutationObserver | ||
targetElement?: HTMLElement | ||
parentElement?: HTMLElement | ||
} | ||
|
||
const sourceMap = new Map<Symbol, Omit<UseWatermarkRes, 'clearAll'>>() | ||
|
||
function createBase64(str: string) { | ||
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' | ||
cans.fillText(str, width / 20, height) | ||
// todo 自定义水印样式 | ||
} | ||
return can.toDataURL('image/png') | ||
} | ||
const resetWatermarkStyle = (element: HTMLElement, watermarkText: string) => { | ||
element.className = '__' + watermarkSymbol | ||
element.style.pointerEvents = 'none' | ||
element.style.top = '0px' | ||
element.style.left = '0px' | ||
element.style.position = 'absolute' | ||
element.style.zIndex = '100000' | ||
element.style.height = '100%' | ||
element.style.width = '100%' | ||
element.style.background = `url(${createBase64( | ||
unref(updateWatermarkText) || watermarkText, | ||
)}) 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, | ||
) | ||
if (!target) return | ||
const { targetElement, parentElement } = target | ||
// 父元素的子元素水印如果被删除 重新插入被删除的水印(防篡改,插入通过控制台删除的水印) | ||
if (!parentElement?.contains(targetElement as Node | null)) { | ||
target?.parentElement?.appendChild(node as HTMLElement) | ||
} | ||
} | ||
if (mutation.attributeName === 'style' && mutation.target) { | ||
const _target = mutation.target as HTMLElement | ||
if (_target.className === '__' + watermarkSymbol) { | ||
resetWatermarkStyle( | ||
_target as HTMLElement, | ||
_target?.['data-watermark-text'], | ||
) | ||
} | ||
} | ||
} | ||
}) | ||
return obInstance | ||
} | ||
|
||
export function useWatermark( | ||
appendEl: Ref<HTMLElement | null> = ref(document.body) as Ref<HTMLElement>, | ||
): UseWatermarkRes { | ||
const domSymbol = Symbol(watermarkSymbol) | ||
const appendElRaw = unref(appendEl) | ||
if (appendElRaw && sourceMap.has(domSymbol)) { | ||
const { setWatermark, clear } = sourceMap.get(domSymbol) as UseWatermarkRes | ||
return { | ||
setWatermark, | ||
clear, | ||
clearAll, | ||
} | ||
} | ||
const func = useThrottleFn(function () { | ||
const el = unref(appendEl) | ||
if (!el) return | ||
const { clientHeight: height, clientWidth: width } = el | ||
updateWatermark({ height, width }) | ||
}) | ||
const watermarkEl = shallowRef<HTMLElement>() | ||
const clear = () => { | ||
const domId = unref(watermarkEl) | ||
watermarkEl.value = undefined | ||
const el = unref(appendEl) | ||
sourceMap.has(domSymbol) && | ||
sourceMap.get(domSymbol)?.obInstance?.disconnect() | ||
sourceMap.delete(domSymbol) | ||
if (!el) return | ||
domId && el.removeChild(domId) | ||
removeResizeListener(el, func) | ||
} | ||
|
||
function updateWatermark( | ||
options: { | ||
width?: number | ||
height?: number | ||
str?: string | ||
} = {}, | ||
) { | ||
const el = unref(watermarkEl) | ||
if (!el) return | ||
if (!isUndefined(options.width)) { | ||
el.style.width = `${options.width}px` | ||
} | ||
if (!isUndefined(options.height)) { | ||
el.style.height = `${options.height}px` | ||
} | ||
if (!isUndefined(options.str)) { | ||
el.style.background = `url(${createBase64(options.str)}) left top repeat` | ||
} | ||
} | ||
|
||
const createWatermark = (str: string) => { | ||
if (unref(watermarkEl) && sourceMap.has(domSymbol)) { | ||
updateWatermarkText.value = str | ||
updateWatermark({ str }) | ||
return | ||
} | ||
const div = document.createElement('div') | ||
div['data-watermark-text'] = str //自定义属性 用于恢复水印 | ||
updateWatermarkText.value = str | ||
watermarkEl.value = div | ||
resetWatermarkStyle(div, str) | ||
const el = unref(appendEl) | ||
if (!el) return | ||
const { clientHeight: height, clientWidth: width } = el | ||
updateWatermark({ str, width, height }) | ||
el.appendChild(div) | ||
sourceMap.set(domSymbol, { | ||
setWatermark, | ||
clear, | ||
parentElement: el, | ||
targetElement: div, | ||
obInstance: obFn(), | ||
}) | ||
sourceMap.get(domSymbol)?.obInstance?.observe(el, { | ||
childList: true, // 子节点的变动(指新增,删除或者更改) | ||
subtree: true, // 该观察器应用于该节点的所有后代节点 | ||
attributes: true, // 属性的变动 | ||
}) | ||
} | ||
|
||
function setWatermark(str: string) { | ||
createWatermark(str) | ||
addResizeListener(document.documentElement, func) | ||
const instance = getCurrentInstance() | ||
if (instance) { | ||
onBeforeUnmount(() => { | ||
clear() | ||
}) | ||
} | ||
} | ||
|
||
return { | ||
setWatermark, | ||
clear, | ||
clearAll, | ||
} | ||
} | ||
|
||
function clearAll() { | ||
Array.from(sourceMap.values()).forEach((item) => { | ||
item?.obInstance?.disconnect() | ||
item.clear() | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
<script lang="ts" setup> | ||
import { ref, onUnmounted, nextTick } from 'vue' | ||
import type { Ref } from 'vue' | ||
import { useWatermark } from '@/hooks/web/useWatermark' | ||
const { setWatermark, clear, clearAll } = useWatermark() | ||
const { setWatermark: setWatermark2 } = useWatermark() | ||
onUnmounted(() => { | ||
clearAll() | ||
}) | ||
const show = ref(false) | ||
const content = '我的水印' | ||
const createWatermark = (cardEl) => { | ||
useWatermark(cardEl as Ref<HTMLElement | null>).setWatermark(content) | ||
} | ||
nextTick(() => { | ||
createWatermark(document.getElementById('cardId')) | ||
}) | ||
</script> | ||
|
||
<template> | ||
<VbenCard title="水印示例"> | ||
<VbenWatermark | ||
v-if="show" | ||
content="大家艰苦一下,一切都会有的" | ||
cross | ||
fullscreen | ||
:font-size="16" | ||
:line-height="16" | ||
:width="384" | ||
:height="384" | ||
:x-offset="12" | ||
:y-offset="60" | ||
:rotate="-15" | ||
/> | ||
<VbenGrid x-gap="12" :cols="2"> | ||
<VbenGridItem> | ||
<VbenCard title="组件方式" style="height: 100%"> | ||
<div class="mb-4">基于Naive UI的水印Watermark组件</div> | ||
<div> | ||
<VbenCard title="全屏水印"> | ||
<VbenSwitch v-model:value="show" /> | ||
</VbenCard> | ||
<VbenCard title="部分水印"> | ||
<VbenWatermark | ||
content="核心机密" | ||
cross | ||
selectable | ||
:font-size="16" | ||
:line-height="16" | ||
:width="192" | ||
:height="128" | ||
:x-offset="12" | ||
:y-offset="28" | ||
:rotate="-15" | ||
> | ||
<VbenCard title="带封面的卡片" hoverable> | ||
<template #cover> | ||
<img src="@/assets/images/cover.png" alt="cover.png" /> | ||
</template> | ||
样式丰富了许多,不是吗🥳 | ||
</VbenCard> | ||
</VbenWatermark> | ||
</VbenCard> | ||
</div> | ||
</VbenCard> | ||
</VbenGridItem> | ||
<VbenGridItem> | ||
<VbenCard title="函数方式" style="height: 100%"> | ||
<div class="mb-4">基于自定义hooks实现(支持防篡改)</div> | ||
<div> | ||
<VbenButton | ||
type="primary" | ||
class="mr-2" | ||
@click="setWatermark('WaterMark Info1')" | ||
> | ||
Create Watermark1 | ||
</VbenButton> | ||
<VbenButton | ||
type="primary" | ||
class="mr-2" | ||
@click="setWatermark2('WaterMark Info2')" | ||
> | ||
Create Watermark2 | ||
</VbenButton> | ||
<VbenButton type="error" class="mr-2" @click="clear"> | ||
Clear Watermark1 | ||
</VbenButton> | ||
<VbenButton type="error" class="mr-2" @click="clearAll"> | ||
ClearAll | ||
</VbenButton> | ||
<VbenButton | ||
type="warning" | ||
class="mr-2" | ||
@click="setWatermark('WaterMark Info New')" | ||
> | ||
Update Watermark1 | ||
</VbenButton> | ||
<VbenCard title="部分水印"> | ||
<VbenCard id="cardId" title="带封面的卡片" hoverable> | ||
<template #cover> | ||
<img src="@/assets/images/cover.png" alt="cover.png" /> | ||
</template> | ||
样式丰富了许多,不是吗🥳 | ||
</VbenCard> | ||
</VbenCard> | ||
</div> | ||
</VbenCard> | ||
</VbenGridItem> | ||
</VbenGrid> | ||
</VbenCard> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
<script lang="ts" setup name="VbenWatermark"> | ||
import { maps } from '#/index' | ||
const Watermark = maps.get('Watermark') | ||
</script> | ||
<template> | ||
<Watermark v-bind="$attrs"> | ||
<template #[item]="data" v-for="item in Object.keys($slots)" :key="item"> | ||
<slot :name="item" v-bind="data || {}"></slot> </template | ||
></Watermark> | ||
</template> | ||
|
||
<style scoped></style> |