uniapp 图片添加水印代码封装(优化版、图片上传压缩、生成文字根据页面自适应比例、增加文字背景色
多张照片上传封装
c
<template>
<view class="image-picker">
<uni-file-picker v-model="imageValue" :auto-upload="false" :title="title" :limit="limit"
:image-styles="imageStyles" :file-mediatype="fileMediatype" :mode="mode" @select="select">
<view v-if="loading" class="form-item-column-center">
<u-loading-icon text="上传中" textSize="12" :vertical="true"></u-loading-icon>
</view>
<view v-else class="form-item-column-center">
<u-icon name="camera" size="28"></u-icon>
<view :style="{ marginTop: '5px'}">上传照片</view>
</view>
</uni-file-picker>
<view class="watermark-canvas">
<canvas id="watermark-canvas" :style="{ width: canvasWidth, height: canvasHeight }"
canvas-id="watermark-canvas" v-if="flag" />
</view>
</view>
</template>
<script>
import {
imageUpload,
} from '@/api/system/applet.js' //图片上传
import {
imageChoose, //拿到后台图片上传链接
} from '@/utils/public.js'
export default {
name: 'ImageWatermarkPicker',
props: {
limit: {
type: [Number, String],
default: 1,
},
title: {
type: String,
default: null,
},
mode: {
type: String,
default: 'grid',
},
fileMediatype: {
type: String,
default: 'image',
},
imageStyles: {
type: Object,
default: null,
},
watermark: {
type: Boolean,
default: true,
},
// #ifdef VUE3
modelValue: {
type: Array,
default () {
return []
},
},
// #endif
// #ifndef VUE3
value: {
type: Array,
default () {
return []
},
},
// #endif
},
emits: ['input', 'update:modelValue'],
data() {
return {
flag: false, //绘制显示
imageValue: [],
canvasWidth: '1080px',
canvasHeight: '2160px',
longitude: '', //坐标
latitude: '', //y坐标
addressName: '', //传入公司地址
loading: false,
oldImageslength: null, //上传时照片个数
}
},
watch: {
imageValue(newVal) {
// #ifdef VUE3
this.$emit('update:modelValue', newVal)
// #endif
// #ifndef VUE3
this.$emit('input', newVal)
// #endif
this.$emit('change', newVal)
},
// #ifndef VUE3
value: {
handler(newVal) {
this.imageValue = newVal
},
immediate: true,
},
// #endif
// #ifdef VUE3
modelValue: {
handler(newVal) {
this.imageValue = newVal
},
immediate: true,
},
// #endif
},
methods: {
// 检测图片,确保图片存在
checkImage(url) {
const checkNum = 5
let currentCheckNum = 1
return new Promise((resolve, reject) => {
process()
function process() {
uni.getImageInfo({
src: url,
success: function(image) {
resolve(image)
},
fail: function(err) {
if (checkNum <= currentCheckNum) {
uni.showToast({
title: '图片上传失败',
icon: 'none'
})
reject(err)
} else {
currentCheckNum++
const timer = setTimeout(() => {
clearTimeout(timer)
process()
}, 300)
}
},
})
}
})
},
async select(e) {
this.oldImageslength = e.tempFiles.length
for (let tempFile of e.tempFiles) {
await this.watermarkProcess(tempFile)
}
},
async watermarkProcess(tempFile) {
const {
name,
size,
extname,
uuid,
path
} = tempFile
let url = null
let photo = null
// 添加水印
if (this.watermark) {
url = await this.addWatermark(path)
}
// 上传图片
url = await this.uploadFile(url)
// 检测图片,确保图片存在
await this.checkImage(url)
this.imageValue = [
...this.imageValue,
{
name,
extname,
url,
photo,
size,
uuid,
},
]
},
getHeightOffset(height, index, fontSize) {
return index == 0 ? (height - fontSize) : (height - (index * fontSize) - fontSize) - (index * (fontSize /
3))
},
// 异步添加水印
async addWatermark(tempFilePath) {
// #ifdef MP-WEIXIN
this.addressName = '测试位置'
this.latitude = 119.651
this.longitude = 80.654
// #endif
this.flag = true
if (this.loading == true) {
uni.showLoading({
title: "上传图片",
mask: true,
})
}
return new Promise((resolve, reject) => {
uni.getImageInfo({
src: tempFilePath,
success: async (res) => {
// 设置画布高度和宽度
this.canvasWidth = `${res.width}px`
this.canvasHeight = `${res.height}px`
await this.sleep(200) // 某些平台 canvas 渲染慢,需要等待
const ctx = uni.createCanvasContext('watermark-canvas', this)
ctx.clearRect(0, 0, res.width, res.height)
ctx.beginPath()
ctx.drawImage(tempFilePath, 0, 0)// 第一个参数是图片 第二、三是图片在画布位置 第四、五是将图片绘制成多大宽高(不写四五就是原图宽高)
let size
// 根据图片纵横比设置字体大小,背景色相比一般
ctx.fillStyle = 'rgba(0,0,0,0.1)'; // 设置背景色
if (res.width / res.height > 1) {
size = Math.floor(res.height / 26)
//这个背景不一定适用,自行调整
ctx.fillRect(0, res.height - (size*6), res.width, size*6); // 填充整个 Canvas 区域
} else {
size = Math.floor(res.width / 26)
ctx.fillRect(0, res.height - (size*7), res.width, size*7); // 填充整个 Canvas 区域
}
let fontSize = size
ctx.setFontSize(fontSize)
ctx.shadowColor = "rgba(0,0,0,1.0)";//设置字体阴影,真机未生效
ctx.shadowOffsetX = 5
ctx.shadowOffsetY = 5
let max = (res.width - fontSize) / fontSize //图片上一行能显示的最大字数
let marks = []
let address = "地址:" + this.addressName
let location = "坐标:" + this.latitude + ',' + this.longitude
let fillTexts = [address, location, time]
fillTexts.forEach((mark, index) => {
//测量出最长文字的宽
// console.log('文字宽:'+ctx.measureText(mark).width)
if (mark.length <= max) {
marks.push({
mark: mark, //水印文字
start: fontSize / 2 //第一个字的起点位置
})
} else {
marks.push({
mark: mark.substring(max),
start: fontSize / 2 + fontSize *
3 //第一个字的起点位置,+fontSize*3是因为此水印为当前mark的换行,因此缩进3字符
})
marks.push({
mark: mark.substring(0, max),
start: fontSize / 2 //第一个字的起点位置
})
}
})
// 绘制水印背景另外写法,实测不太好用
// ctx.fillStyle = 'rgba(0,0,0,0.1)'; // 设置背景色
// ctx.fillRect(0, this.getHeightOffset(res.height, 4, fontSize), res.width,this.getHeightOffset(res.height, 4, fontSize)); // 填充整个 Canvas 区域
//绘制水印文字
marks.forEach((mark, index) => {
ctx.setFillStyle("rgba(250, 250, 250,1.0)")
ctx.fillText(mark.mark, mark.start, this.getHeightOffset(
res.height, index, fontSize))
});
ctx.draw(false, async () => {
await this.sleep(500) // 某些平台 canvas 渲染慢,需要等待
uni.canvasToTempFilePath({
canvasId: 'watermark-canvas',
destWidth: res.width,
destHeight: res.height,
fileType: 'jpg',
quality: 0.8,
success: (fileRes) => {
this.flag = false
resolve(fileRes.tempFilePath)
},
fail: (err) => {
console.log('[Error draw]', err)
uni.showToast({
title: err.errMsg,
icon: 'none'
})
reject()
},
},
this,
)
})
},
fail: (err) => {
console.log('[Error getImageInfo]', err)
uni.showToast({
title: err.errMsg,
icon: 'none'
})
reject()
},
})
})
},
//此位置为我上传后台的写法,具体可按照自己的填写
async uploadFile(path) {
let image = imageChoose(path)
const res = await imageUpload(image).then(
response => {
this.oldImageslength--
this.loading = this.oldImageslength == 0 ? false : true
this.oldImageslength == 0 ? uni.hideLoading() : ''
return response.data.url
})
return res
},
sleep(millisecond) {
return new Promise((resolve) => {
setTimeout(resolve, millisecond)
})
},
},
}
</script>
<style lang="scss" scoped>
canvas {
position: absolute;
left: 2000upx;
}
.image-picker {
position: relative;
.form-item-column-center {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
flex-direction: column;
}
.watermark-canvas {
position: absolute;
top: 5px;
left: 5px;
width: 1px;
height: 1px;
overflow: hidden;
}
}
</style>
应用实例
照片上传实例
c
<template>
<view>
<photoList v-model="baseFormData.faceImgsFirst" :limit="1"/>
</view>
</template>
<script>
import photoOne from '@/pages/public/photoOne/photoOne.vue'
export default{
components: {
photoOne
},
data(){
return{
baseFormData:{}
}
},
methods:{
}
}
</script>
动态表单照片添加水印(直接使用)
注意imagelists必填,避免出现删除不一致现象,发送为父级数据
c
<template>
<view>
<uni-forms>
<uni-forms-item label="照片" required :rules="[{required: true,errorMessage: '最少一张照片'}]":name="['inspectionCustodyWorkLogDetailBoList',index,'imagelist']" label-width="100rpx">
<view class="form-item">
<photoList v-model="baseFormData.faceImgsFirst" :limit="1"/>
</view>
</uni-forms-item>
</uni-forms>
</view>
</template>
<script>
import photoOne from '@/pages/public/photoOne/photoOne.vue'
export default{
components: {
photoOne
},
data(){
// 基础表单数据
baseFormData: {
inspectionCustodyWorkLogDetailBoList: [],
},
},
methods:{
}
}
</script>