uniapp 图片添加水印代码封装(优化版、图片上传压缩、生成文字根据页面自适应比例、增加文字背景色

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>
相关推荐
masa01017 分钟前
JavaScript--JavaScript基础
开发语言·javascript
让开,我要吃人了2 小时前
HarmonyOS开发实战(5.0)实现二楼上划进入首页效果详解
前端·华为·程序员·移动开发·harmonyos·鸿蒙·鸿蒙系统
Passion不晚2 小时前
Vue vs React vs Angular 的对比和选择
vue.js·react.js·前端框架·angular.js
everyStudy3 小时前
前端五种排序
前端·算法·排序算法
甜兒.4 小时前
鸿蒙小技巧
前端·华为·typescript·harmonyos
她似晚风般温柔7897 小时前
Uniapp + Vue3 + Vite +Uview + Pinia 分商家实现购物车功能(最新附源码保姆级)
开发语言·javascript·uni-app
Jiaberrr8 小时前
前端实战:使用JS和Canvas实现运算图形验证码(uniapp、微信小程序同样可用)
前端·javascript·vue.js·微信小程序·uni-app
everyStudy8 小时前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
城南云小白8 小时前
web基础+http协议+httpd详细配置
前端·网络协议·http
前端小趴菜、8 小时前
Web Worker 简单使用
前端