496 lines
13 KiB
Vue
496 lines
13 KiB
Vue
<!--
|
||
* @Author: weisheng
|
||
* @Date: 2023-04-05 21:32:56
|
||
* @LastEditTime: 2025-01-16 21:43:47
|
||
* @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>
|