货无忧
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

576 lines
15 KiB

<!--
* @Author: weisheng
* @Date: 2023-04-05 21:32:56
* @LastEditTime: 2023-05-15 11:29:31
* @LastEditors: weisheng
* @Description: 水印组件
* @FilePath: \fant-mini-plus\src\uni_modules\fant-mini-plus\components\hd-water-mark\hd-water-mark.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 {
// 将自定义节点设置成虚拟的更加接近Vue组件的表现可以去掉微信小程序自定义组件多出的最外层标签
options: {
virtualHost: true
}
}
</script>
<script lang="ts" setup>
import { computed, onMounted, ref, watch, nextTick } from 'vue'
import { CommonUtil } from '../..'
/**
* WaterMark 水印
*/
interface Props {
// 显示内容
content?: string
// 显示图片的地址
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: 64,
// 图片高度
imageWidth: 120,
// X轴间距
gutterX: 24,
// Y轴间距
gutterY: 48,
// canvas画布宽度,单位px
width: 120,
// canvas画布高度,单位px
height: 64,
// 是否为全屏水印
fullScreen: true,
// 水印字体颜色
color: '#1a1a1a',
// 水印字体大小
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${CommonUtil.s4()}`) // 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 = 'hd-water-mark'
if (props.fullScreen) {
classess = `${classess} hd-water-mark-full-screen`
}
return classess
})
/**
* 水印样式
*/
const rootStyle = computed(() => {
const style: Record<string, string | number> = {
opacity: props.opacity,
backgroundSize: CommonUtil.addUnit(props.width + props.gutterX, 'px')
}
if (waterMarkUrl.value) {
style['backgroundImage'] = `url('${waterMarkUrl.value}')`
}
return CommonUtil.style(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'
img.src = CommonUtil.setUrlParams(image, {
timestamp: `${new Date().getTime()}`
}) // 要加载的图片 url
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>
.hd-water-mark {
position: absolute;
opacity: 0.1;
z-index: 1010;
left: 0;
right: 0;
top: 0;
bottom: 0;
pointer-events: none;
background-repeat: repeat;
&-full-screen {
position: fixed;
}
}
</style>