UNPKG

hongluan-ui

Version:
1 lines 8.28 kB
{"version":3,"file":"watermark.mjs","sources":["../../../../../../packages/components/watermark/src/watermark.vue"],"sourcesContent":["<template>\n <div ref=\"containerRef\" :style=\"[style]\">\n <slot></slot>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport {\n computed,\n onBeforeUnmount,\n onMounted,\n ref,\n shallowRef,\n watch,\n} from 'vue'\nimport { useMutationObserver } from '@vueuse/core'\nimport { watermarkProps } from './watermark'\nimport { getPixelRatio, getStyleStr, reRendering } from './utils'\nimport useClips, { FontGap } from './useClips'\nimport type { WatermarkProps, WatermarkFontType } from './watermark'\nimport type { CSSProperties } from 'vue'\n\ndefineOptions({\n name: 'Watermark',\n})\n\nconst style: CSSProperties = {\n position: 'relative',\n}\n\nconst props = defineProps(watermarkProps)\n\nconst color = computed(() => (props.font as WatermarkFontType)?.color ?? 'rgba(0,0,0,.15)')\nconst fontSize = computed(() => (props.font as WatermarkFontType)?.fontSize ?? 16)\nconst fontWeight = computed(() => (props.font as WatermarkFontType)?.fontWeight ?? 'normal')\nconst fontStyle = computed(() => (props.font as WatermarkFontType)?.fontStyle ?? 'normal')\nconst fontFamily = computed(() => (props.font as WatermarkFontType)?.fontFamily ?? 'sans-serif')\nconst textAlign = computed(() => (props.font as WatermarkFontType)?.textAlign ?? 'center')\nconst textBaseline = computed(() => (props.font as WatermarkFontType)?.textBaseline ?? 'hanging')\n\nconst gapX = computed(() => props.gap[0])\nconst gapY = computed(() => props.gap[1])\nconst gapXCenter = computed(() => gapX.value / 2)\nconst gapYCenter = computed(() => gapY.value / 2)\nconst offsetLeft = computed(() => props.offset?.[0] ?? gapXCenter.value)\nconst offsetTop = computed(() => props.offset?.[1] ?? gapYCenter.value)\n\nconst getMarkStyle = () => {\n const markStyle: CSSProperties = {\n zIndex: props.zIndex,\n position: 'absolute',\n left: 0,\n top: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none',\n backgroundRepeat: 'repeat',\n }\n\n /** Calculate the style of the offset */\n let positionLeft = offsetLeft.value - gapXCenter.value\n let positionTop = offsetTop.value - gapYCenter.value\n if (positionLeft > 0) {\n markStyle.left = `${positionLeft}px`\n markStyle.width = `calc(100% - ${positionLeft}px)`\n positionLeft = 0\n }\n if (positionTop > 0) {\n markStyle.top = `${positionTop}px`\n markStyle.height = `calc(100% - ${positionTop}px)`\n positionTop = 0\n }\n markStyle.backgroundPosition = `${positionLeft}px ${positionTop}px`\n\n return markStyle\n}\n\nconst containerRef = shallowRef<HTMLDivElement | null>(null)\nconst watermarkRef = shallowRef<HTMLDivElement>()\nconst stopObservation = ref(false)\n\nconst destroyWatermark = () => {\n if (watermarkRef.value) {\n watermarkRef.value.remove()\n watermarkRef.value = undefined\n }\n}\nconst appendWatermark = (base64Url: string, markWidth: number) => {\n if (containerRef.value && watermarkRef.value) {\n stopObservation.value = true\n watermarkRef.value.setAttribute(\n 'style',\n getStyleStr({\n ...getMarkStyle(),\n backgroundImage: `url('${base64Url}')`,\n backgroundSize: `${Math.floor(markWidth)}px`,\n }),\n )\n containerRef.value?.append(watermarkRef.value)\n // Delayed execution\n setTimeout(() => {\n stopObservation.value = false\n })\n }\n}\n\n/**\n * Get the width and height of the watermark. The default values are as follows\n * Image: [120, 64]; Content: It's calculated by content;\n */\nconst getMarkSize = (ctx: CanvasRenderingContext2D) => {\n let defaultWidth = 120\n let defaultHeight = 64\n const image = props.image\n const content = props.content\n const width = props.width\n const height = props.height\n if (!image && ctx.measureText) {\n ctx.font = `${Number(fontSize.value)}px ${fontFamily.value}`\n const contents = Array.isArray(content) ? content : [content]\n const sizes = contents.map(item => {\n const metrics = ctx.measureText(item!)\n\n return [\n metrics.width,\n // Using `actualBoundingBoxAscent` to be compatible with lower version browsers (eg: Firefox < 116)\n metrics.fontBoundingBoxAscent !== undefined\n ? metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent\n : metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent,\n ]\n })\n defaultWidth = Math.ceil(Math.max(...sizes.map(size => size[0])))\n defaultHeight =\n Math.ceil(Math.max(...sizes.map(size => size[1]))) * contents.length +\n (contents.length - 1) * FontGap\n }\n return [width ?? defaultWidth, height ?? defaultHeight] as const\n}\n\nconst getClips = useClips()\n\nconst renderWatermark = () => {\n const canvas = document.createElement('canvas')\n const ctx = canvas.getContext('2d')\n const image = props.image\n const content = props.content\n const rotate = props.rotate\n\n if (ctx) {\n if (!watermarkRef.value) {\n watermarkRef.value = document.createElement('div')\n }\n\n const ratio = getPixelRatio()\n const [markWidth, markHeight] = getMarkSize(ctx)\n\n const drawCanvas = (\n drawContent?: NonNullable<WatermarkProps['content']> | HTMLImageElement,\n ) => {\n const [textClips, clipWidth] = getClips(\n drawContent || '',\n rotate,\n ratio,\n markWidth,\n markHeight,\n {\n color: color.value,\n fontSize: fontSize.value,\n fontStyle: fontStyle.value,\n fontWeight: fontWeight.value,\n fontFamily: fontFamily.value,\n textAlign: textAlign.value,\n textBaseline: textBaseline.value,\n },\n gapX.value,\n gapY.value,\n )\n\n appendWatermark(textClips, clipWidth)\n }\n\n if (image) {\n const img = new Image()\n img.onload = () => {\n drawCanvas(img)\n }\n img.onerror = () => {\n drawCanvas(content)\n }\n img.crossOrigin = 'anonymous'\n img.referrerPolicy = 'no-referrer'\n img.src = image\n } else {\n drawCanvas(content)\n }\n }\n}\n\nonMounted(() => {\n renderWatermark()\n})\n\nwatch(\n () => props,\n () => {\n renderWatermark()\n },\n {\n deep: true,\n flush: 'post',\n },\n)\n\nonBeforeUnmount(() => {\n destroyWatermark()\n})\n\nconst onMutate = (mutations: MutationRecord[]) => {\n if (stopObservation.value) {\n return\n }\n mutations.forEach(mutation => {\n if (reRendering(mutation, watermarkRef.value)) {\n destroyWatermark()\n renderWatermark()\n }\n })\n}\n\nuseMutationObserver(containerRef, onMutate, {\n attributes: true,\n subtree: true,\n childList: true,\n})\n</script>\n"],"names":[],"mappings":";;;;;;oCAsBc;AAAA,EACZ,MAAM;AACR;;;;;;AAEA,UAAM,QAAuB;AAAA,MAC3B,UAAU;AAAA;AAKZ,UAAM,QAAQ,SAAS,MAAO;AAC9B,UAAM;AACN,4CAAyC,oBAA0C;AACnF;AACA,UAAM;AACN,UAAM;AACN,yBAAqB,eAAgB,YAAkC;AAEvE;AACA,UAAM,sBAAsB,MAAM;AAClC,UAAM;AACN,oDAAgD;AAChD;AACA,UAAM,YAAY,SAAS,MAAM;AAEjC,UAAM;AACJ;AAAiC;AACjB;AACJ,cACJ;AAAA,aACD;AAAA;AACE;AACC;AACO;AACG,MACpB;AAGA,UAAI,eAAe;AACnB,UAAI;AACJ,yBAAmB;AACjB;AACA,0BAAkB,eAAe;AACjC;AAAe;AAEjB,UAAI;AACF;AACA;AACA,sBAAc;AAAA,MAChB;AACA,qCAA+B;AAE/B;AAAO;AAGT;AACA,UAAM,eAAe;AACrB;AAEA;AACE;AACE;AACA;AAAqB;AACvB;AAEF,6BAAyB;AACvB;AACE;AACA,6DAEc;AAAA;AACM,UAChB;AAAyB,0BACT,cAAc;AAAS,QACzC;AAEF;AAEA;AACE;AAAwB,QAC1B;AAAC;AACH;AAOF;AACE;AACA;AACA;AACA,sBAAgB;AAChB;AACA;AACA,UAAI;AACF,sBAAc;AACd;AACA,mCAA2B;AACzB;AAEA;AAAO;AACG,YAER;AAE8C,UAChD;AAAA,QACF;AACA;AACA;AAE0B;AAE5B;AAAsD;AAGxD;AAEA;AACE;AACA;AACA;AACA;AACA,qBAAe;AAEf,UAAI;AACF,0BAAkB;AAChB,+BAAqB;AAA4B;AAGnD;AACA,0BAAkB;AAElB;AAGE,gBAAM,YAAY,sBAChB,mBACA;AAIA,yBACe;AAAA,sBACH,SAAS;AAAA,uBACR;AAAU;AACE,YACvB;AAAuB,YACvB;AAAqB,YACrB;AAA2B,UAC7B;AAKF,qCAA2B;AAAS,QACtC;AAEA;AACE;AACA;AACE;AAAc,UAChB;AACA,8BAAoB;AAClB;AAAkB;AAEpB,4BAAkB;AAClB;AACA;AAAU,QACZ;AACE,4BAAkB;AAAA;AACpB;AACF;AAGF,cAAU;AACR;AAAgB;AAGlB;AAGI;AAAgB;AAElB,YACQ;AAAA;AACC;AAIX;AACE;AAAiB;AAGnB;AACE,UAAI,gBAAgB,OAAO;AACzB;AAAA;AAEF;AACE,wBAAgB;AACd;AACA;AAAgB;AAClB;AACD;AAGH;AAA4C;AAC9B;AACH;AACE,KACZ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}