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
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>
|
|
|