| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495 |
- <!--
- * @Author: weisheng
- * @Date: 2023-04-05 21:32:56
- * @LastEditTime: 2025-04-28 19:41:17
- * @LastEditors: weisheng
- * @Description: 水印组件
- * @FilePath: /wot-design-uni/src/uni_modules/wot-design-uni/components/wd-watermark/wd-watermark.vue
- * 记得注释
- -->
- <template>
- <view :class="rootClass" :style="rootStyle">
- <canvas
- v-if="!canvasOffScreenable && showCanvas"
- type="2d"
- :style="{ height: canvasHeight + 'px', width: canvasWidth + 'px', visibility: 'hidden' }"
- :canvas-id="canvasId"
- :id="canvasId"
- />
- </view>
- </template>
- <script lang="ts">
- export default {
- name: 'wd-watermark',
- options: {
- addGlobalClass: true,
- virtualHost: true,
- styleIsolation: 'shared'
- }
- }
- </script>
- <script lang="ts" setup>
- import { computed, onMounted, ref, watch, nextTick, type CSSProperties } from 'vue'
- import { addUnit, buildUrlWithParams, isBase64Image, objToStyle, uuid } from '../common/util'
- import { watermarkProps } from './types'
- const props = defineProps(watermarkProps)
- watch(
- () => props,
- () => {
- doReset()
- },
- { deep: true }
- )
- const canvasId = ref<string>(`water${uuid()}`) // canvas 组件的唯一标识符
- const waterMarkUrl = ref<string>('') // canvas生成base64水印
- const canvasOffScreenable = ref<boolean>(uni.canIUse('createOffscreenCanvas') && Boolean(uni.createOffscreenCanvas)) // 是否可以使用离屏canvas
- const pixelRatio = ref<number>(uni.getSystemInfoSync().pixelRatio) // 像素比
- const canvasHeight = ref<number>((props.height + props.gutterY) * pixelRatio.value) // canvas画布高度
- const canvasWidth = ref<number>((props.width + props.gutterX) * pixelRatio.value) // canvas画布宽度
- const showCanvas = ref<boolean>(true) // 是否展示canvas
- /**
- * 水印css类
- */
- const rootClass = computed(() => {
- let classess: string = 'wd-watermark'
- if (props.fullScreen) {
- classess = `${classess} is-fullscreen`
- }
- return `${classess} ${props.customClass}`
- })
- /**
- * 水印样式
- */
- const rootStyle = computed(() => {
- const style: CSSProperties = {
- opacity: props.opacity,
- backgroundSize: addUnit(props.width + props.gutterX)
- }
- if (waterMarkUrl.value) {
- style['backgroundImage'] = `url('${waterMarkUrl.value}')`
- }
- return `${objToStyle(style)}${props.customStyle}`
- })
- onMounted(() => {
- doInit()
- })
- function doReset() {
- showCanvas.value = true
- canvasHeight.value = (props.height + props.gutterY) * pixelRatio.value
- canvasWidth.value = (props.width + props.gutterX) * pixelRatio.value
- nextTick(() => {
- doInit()
- })
- }
- function doInit() {
- // #ifdef H5
- // h5使用document.createElement创建canvas,不用展示canvas标签
- showCanvas.value = false
- // #endif
- const { width, height, color, size, fontStyle, fontWeight, fontFamily, content, rotate, gutterX, gutterY, image, imageHeight, imageWidth } = props
- // 创建水印
- createWaterMark(width, height, color, size, fontStyle, fontWeight, fontFamily, content, rotate, gutterX, gutterY, image, imageHeight, imageWidth)
- }
- /**
- * 创建水印图片
- * @param width canvas宽度
- * @param height canvas高度
- * @param color canvas字体颜色
- * @param size canvas字体大小
- * @param fontStyle canvas字体样式
- * @param fontWeight canvas字体字重
- * @param fontFamily canvas字体系列
- * @param content canvas内容
- * @param rotate 倾斜角度
- * @param gutterX X轴间距
- * @param gutterY Y轴间距
- * @param image canvas图片
- * @param imageHeight canvas图片高度
- * @param imageWidth canvas图片宽度
- */
- function createWaterMark(
- width: number,
- height: number,
- color: string,
- size: number,
- fontStyle: string,
- fontWeight: number | string,
- fontFamily: string,
- content: string,
- rotate: number,
- gutterX: number,
- gutterY: number,
- image: string,
- imageHeight: number,
- imageWidth: number
- ) {
- const canvasHeight = (height + gutterY) * pixelRatio.value
- const canvasWidth = (width + gutterX) * pixelRatio.value
- const contentWidth = width * pixelRatio.value
- const contentHeight = height * pixelRatio.value
- const fontSize = size * pixelRatio.value
- // #ifndef H5
- if (canvasOffScreenable.value) {
- createOffscreenCanvas(
- canvasHeight,
- canvasWidth,
- contentWidth,
- contentHeight,
- rotate,
- fontSize,
- fontFamily,
- fontStyle,
- fontWeight,
- color,
- content,
- image,
- imageHeight,
- imageWidth
- )
- } else {
- createCanvas(canvasHeight, contentWidth, rotate, fontSize, color, content, image, imageHeight, imageWidth)
- }
- // #endif
- // #ifdef H5
- createH5Canvas(
- canvasHeight,
- canvasWidth,
- contentWidth,
- contentHeight,
- rotate,
- fontSize,
- fontFamily,
- fontStyle,
- fontWeight,
- color,
- content,
- image,
- imageHeight,
- imageWidth
- )
- // #endif
- }
- /**
- * 创建离屏canvas
- * @param canvasHeight canvas高度
- * @param canvasWidth canvas宽度
- * @param contentWidth 内容宽度
- * @param contentHeight 内容高度
- * @param rotate 内容倾斜角度
- * @param fontSize 字体大小
- * @param fontFamily 字体系列
- * @param fontStyle 字体样式
- * @param fontWeight 字体字重
- * @param color 字体颜色
- * @param content 内容
- * @param image canvas图片
- * @param imageHeight canvas图片高度
- * @param imageWidth canvas图片宽度
- */
- function createOffscreenCanvas(
- canvasHeight: number,
- canvasWidth: number,
- contentWidth: number,
- contentHeight: number,
- rotate: number,
- fontSize: number,
- fontFamily: string,
- fontStyle: string,
- fontWeight: string | number,
- color: string,
- content: string,
- image: string,
- imageHeight: number,
- imageWidth: number
- ) {
- // 创建离屏canvas
- const canvas: any = uni.createOffscreenCanvas({ height: canvasHeight, width: canvasWidth, type: '2d' })
- const ctx: any = canvas.getContext('2d')
- if (ctx) {
- if (image) {
- const img = canvas.createImage() as HTMLImageElement
- drawImageOffScreen(ctx, img, image, imageHeight, imageWidth, rotate, contentWidth, contentHeight, canvas)
- } else {
- drawTextOffScreen(ctx, content, contentWidth, contentHeight, rotate, fontSize, fontFamily, fontStyle, fontWeight, color, canvas)
- }
- } else {
- console.error('无法获取canvas上下文,请确认当前环境是否支持canvas')
- }
- }
- /**
- * 非H5创建canvas
- * 不支持创建离屏canvas时调用
- * @param contentHeight 内容高度
- * @param contentWidth 内容宽度
- * @param rotate 内容倾斜角度
- * @param fontSize 字体大小
- * @param color 字体颜色
- * @param content 内容
- * @param image canvas图片
- * @param imageHeight canvas图片高度
- * @param imageWidth canvas图片宽度
- */
- function createCanvas(
- contentHeight: number,
- contentWidth: number,
- rotate: number,
- fontSize: number,
- color: string,
- content: string,
- image: string,
- imageHeight: number,
- imageWidth: number
- ) {
- const ctx = uni.createCanvasContext(canvasId.value)
- if (ctx) {
- if (image) {
- drawImageOnScreen(ctx, image, imageHeight, imageWidth, rotate, contentWidth, contentHeight)
- } else {
- drawTextOnScreen(ctx, content, contentWidth, rotate, fontSize, color)
- }
- } else {
- console.error('无法获取canvas上下文,请确认当前环境是否支持canvas')
- }
- }
- /**
- * h5创建canvas
- * @param canvasHeight canvas高度
- * @param canvasWidth canvas宽度
- * @param contentWidth 水印内容宽度
- * @param contentHeight 水印内容高度
- * @param rotate 水印内容倾斜角度
- * @param fontSize 水印字体大小
- * @param fontFamily 水印字体系列
- * @param fontStyle 水印字体样式
- * @param fontWeight 水印字体字重
- * @param color 水印字体颜色
- * @param content 水印内容
- */
- function createH5Canvas(
- canvasHeight: number,
- canvasWidth: number,
- contentWidth: number,
- contentHeight: number,
- rotate: number,
- fontSize: number,
- fontFamily: string,
- fontStyle: string,
- fontWeight: string | number,
- color: string,
- content: string,
- image: string,
- imageHeight: number,
- imageWidth: number
- ) {
- const canvas = document.createElement('canvas')
- const ctx = canvas.getContext('2d')
- canvas.setAttribute('width', `${canvasWidth}px`)
- canvas.setAttribute('height', `${canvasHeight}px`)
- if (ctx) {
- if (image) {
- const img = new Image()
- drawImageOffScreen(ctx, img, image, imageHeight, imageWidth, rotate, contentWidth, contentHeight, canvas)
- } else {
- drawTextOffScreen(ctx, content, contentWidth, contentHeight, rotate, fontSize, fontFamily, fontStyle, fontWeight, color, canvas)
- }
- } else {
- console.error('无法获取canvas上下文,请确认当前环境是否支持canvas')
- }
- }
- /**
- * 绘制离屏文字canvas
- * @param ctx canvas上下文
- * @param content 水印内容
- * @param contentWidth 水印宽度
- * @param contentHeight 水印高度
- * @param rotate 水印内容倾斜角度
- * @param fontSize 水印字体大小
- * @param fontFamily 水印字体系列
- * @param fontStyle 水印字体样式
- * @param fontWeight 水印字体字重
- * @param color 水印字体颜色
- * @param canvas canvas实例
- */
- function drawTextOffScreen(
- ctx: CanvasRenderingContext2D,
- content: string,
- contentWidth: number,
- contentHeight: number,
- rotate: number,
- fontSize: number,
- fontFamily: string,
- fontStyle: string,
- fontWeight: string | number,
- color: string,
- canvas: HTMLCanvasElement
- ) {
- ctx.textBaseline = 'middle'
- ctx.textAlign = 'center'
- ctx.translate(contentWidth / 2, contentWidth / 2)
- ctx.rotate((Math.PI / 180) * rotate)
- ctx.font = `${fontStyle} normal ${fontWeight} ${fontSize}px/${contentHeight}px ${fontFamily}`
- ctx.fillStyle = color
- ctx.fillText(content, 0, 0)
- ctx.restore()
- waterMarkUrl.value = canvas.toDataURL()
- }
- /**
- * 绘制在屏文字canvas
- * @param ctx canvas上下文
- * @param content 水印内容
- * @param contentWidth 水印宽度
- * @param rotate 水印内容倾斜角度
- * @param fontSize 水印字体大小
- * @param color 水印字体颜色
- */
- function drawTextOnScreen(ctx: UniApp.CanvasContext, content: string, contentWidth: number, rotate: number, fontSize: number, color: string) {
- ctx.setTextBaseline('middle')
- ctx.setTextAlign('center')
- ctx.translate(contentWidth / 2, contentWidth / 2)
- ctx.rotate((Math.PI / 180) * rotate)
- ctx.setFillStyle(color)
- ctx.setFontSize(fontSize)
- ctx.fillText(content, 0, 0)
- ctx.restore()
- ctx.draw()
- // #ifdef MP-DINGTALK
- // 钉钉小程序的canvasToTempFilePath接口与其他平台不一样
- ;(ctx as any).toTempFilePath({
- success(res: any) {
- showCanvas.value = false
- waterMarkUrl.value = res.filePath
- }
- })
- // #endif
- // #ifndef MP-DINGTALK
- uni.canvasToTempFilePath({
- canvasId: canvasId.value,
- success: (res) => {
- showCanvas.value = false
- waterMarkUrl.value = res.tempFilePath
- }
- })
- // #endif
- }
- /**
- * 绘制离屏图片canvas
- * @param ctx canvas上下文
- * @param img 水印图片对象
- * @param image 水印图片地址
- * @param imageHeight 水印图片高度
- * @param imageWidth 水印图片宽度
- * @param rotate 水印内容倾斜角度
- * @param contentWidth 水印宽度
- * @param contentHeight 水印高度
- * @param canvas canvas实例
- */
- async function drawImageOffScreen(
- ctx: CanvasRenderingContext2D,
- img: HTMLImageElement,
- image: string,
- imageHeight: number,
- imageWidth: number,
- rotate: number,
- contentWidth: number,
- contentHeight: number,
- canvas: HTMLCanvasElement
- ) {
- ctx.translate(contentWidth / 2, contentHeight / 2)
- ctx.rotate((Math.PI / 180) * Number(rotate))
- img.crossOrigin = 'anonymous'
- img.referrerPolicy = 'no-referrer'
- if (isBase64Image(image)) {
- img.src = image
- } else {
- img.src = buildUrlWithParams(image, {
- timestamp: `${new Date().getTime()}`
- })
- }
- img.onload = () => {
- ctx.drawImage(
- img,
- (-imageWidth * pixelRatio.value) / 2,
- (-imageHeight * pixelRatio.value) / 2,
- imageWidth * pixelRatio.value,
- imageHeight * pixelRatio.value
- )
- ctx.restore()
- waterMarkUrl.value = canvas.toDataURL()
- }
- }
- /**
- * 绘制在屏图片canvas
- * @param ctx canvas上下文
- * @param image 水印图片地址
- * @param imageHeight 水印图片高度
- * @param imageWidth 水印图片宽度
- * @param rotate 水印内容倾斜角度
- * @param contentWidth 水印宽度
- * @param contentHeight 水印高度
- */
- function drawImageOnScreen(
- ctx: UniApp.CanvasContext,
- image: string,
- imageHeight: number,
- imageWidth: number,
- rotate: number,
- contentWidth: number,
- contentHeight: number
- ) {
- ctx.translate(contentWidth / 2, contentHeight / 2)
- ctx.rotate((Math.PI / 180) * Number(rotate))
- ctx.drawImage(
- image,
- (-imageWidth * pixelRatio.value) / 2,
- (-imageHeight * pixelRatio.value) / 2,
- imageWidth * pixelRatio.value,
- imageHeight * pixelRatio.value
- )
- ctx.restore()
- ctx.draw(false, () => {
- // #ifdef MP-DINGTALK
- // 钉钉小程序的canvasToTempFilePath接口与其他平台不一样
- ;(ctx as any).toTempFilePath({
- success(res: any) {
- showCanvas.value = false
- waterMarkUrl.value = res.filePath
- }
- })
- // #endif
- // #ifndef MP-DINGTALK
- uni.canvasToTempFilePath({
- canvasId: canvasId.value,
- success: (res) => {
- showCanvas.value = false
- waterMarkUrl.value = res.tempFilePath
- }
- })
- // #endif
- })
- }
- </script>
- <style lang="scss" scoped>
- @import './index.scss';
- </style>
|