|
|
<!--
|
|
|
* @Author: weisheng
|
|
|
* @Date: 2023-04-05 21:32:56
|
|
|
* @LastEditTime: 2023-09-18 22:17:45
|
|
|
* @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 } from 'vue'
|
|
|
import { addUnit, buildUrlWithParams, isBase64Image, objToStyle, uuid } from '../common/util'
|
|
|
|
|
|
/**
|
|
|
* WaterMark 水印
|
|
|
*/
|
|
|
interface Props {
|
|
|
// 显示内容
|
|
|
content?: string
|
|
|
// 显示图片的地址,支持网络图片和base64(钉钉小程序仅支持网络图片)
|
|
|
image?: string
|
|
|
// 图片高度
|
|
|
imageHeight?: number
|
|
|
// 图片高度
|
|
|
imageWidth?: number
|
|
|
// X轴间距,单位px
|
|
|
gutterX?: number
|
|
|
// Y轴间距,单位px
|
|
|
gutterY?: number
|
|
|
// canvas画布宽度,单位px
|
|
|
width?: number
|
|
|
// canvas画布高度,单位px
|
|
|
height?: number
|
|
|
// 是否为全屏水印
|
|
|
fullScreen?: boolean
|
|
|
// 水印字体颜色
|
|
|
color?: string
|
|
|
// 水印字体大小,单位px
|
|
|
size?: number
|
|
|
// 水印字体样式(仅微信和h5支持),可能的值:normal、italic、oblique
|
|
|
fontStyle?: string
|
|
|
// 水印字体的粗细(仅微信和h5支持)
|
|
|
fontWeight?: number | string
|
|
|
// 水印字体系列(仅微信和h5支持)
|
|
|
fontFamily?: string
|
|
|
// 水印旋转角度
|
|
|
rotate?: number
|
|
|
// 自定义层级
|
|
|
zIndex?: number
|
|
|
// 自定义透明度,取值 0~1
|
|
|
opacity?: number
|
|
|
}
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
// 显示内容
|
|
|
content: '',
|
|
|
// 显示图片
|
|
|
image: '',
|
|
|
// 图片高度
|
|
|
imageHeight: 100,
|
|
|
// 图片高度
|
|
|
imageWidth: 100,
|
|
|
// X轴间距
|
|
|
gutterX: 0,
|
|
|
// Y轴间距
|
|
|
gutterY: 0,
|
|
|
// canvas画布宽度,单位px
|
|
|
width: 100,
|
|
|
// canvas画布高度,单位px
|
|
|
height: 100,
|
|
|
// 是否为全屏水印
|
|
|
fullScreen: true,
|
|
|
// 水印字体颜色
|
|
|
color: '#8c8c8c',
|
|
|
// 水印字体大小
|
|
|
size: 14,
|
|
|
// 水印字体样式,可能的值:normal、italic、oblique
|
|
|
fontStyle: 'normal',
|
|
|
// 水印字体的粗细
|
|
|
fontWeight: 'normal',
|
|
|
// 水印字体系列
|
|
|
fontFamily: 'PingFang SC',
|
|
|
// 水印旋转角度
|
|
|
rotate: -25,
|
|
|
// 自定义层级
|
|
|
zIndex: 1100,
|
|
|
// 自定义透明度,取值 0~1
|
|
|
opacity: 0.5
|
|
|
})
|
|
|
|
|
|
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
|
|
|
})
|
|
|
|
|
|
/**
|
|
|
* 水印样式
|
|
|
*/
|
|
|
const rootStyle = computed(() => {
|
|
|
const style: Record<string, string | number> = {
|
|
|
opacity: props.opacity,
|
|
|
backgroundSize: addUnit(props.width + props.gutterX)
|
|
|
}
|
|
|
if (waterMarkUrl.value) {
|
|
|
style['backgroundImage'] = `url('${waterMarkUrl.value}')`
|
|
|
}
|
|
|
return objToStyle(style)
|
|
|
})
|
|
|
|
|
|
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) {
|
|
|
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) {
|
|
|
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>
|