总体思路
dom 结点
这里的 cvHeight
和 cvWidth
初始时要设置为你后续需要压缩后的最大宽高。假设我们在图片上传后图片最大为 350 * 350
<u-upload :fileList="baseInfoFormData.entrustFileList" @afterRead="afterFileRead" multiple></u-upload>
<!-- 添加水印虚拟结点 -->
<view style="width: 0px; height: 0px; overflow: hidden">
<canvas canvas-id="cid3" :style="{
height: `${canvasSize.cvHeight}px`,
width: `${canvasSize.cvWidth}px`,
}"></canvas>
</view>
获取原本图片的宽高
这是一个用于获取图片尺寸的异步函数。它接受一个图片的URL作为参数,并返回一个Promise对象。
这个函数的作用是加载指定URL的图片,获取其宽度和高度,并将它们包装在一个对象中返回。
getImageSizeByUrl(url) {
return new Promise((resolve, reject) => {
// 创建一个新的Image对象,用于加载图片。
const img = new Image();
// 设置图片的src属性为传入的URL,以开始加载图片。
img.src = url;
// 当图片加载完成时,会触发onload事件。
img.onload = function() {
// 在加载成功时,通过resolve函数将图片的宽度和高度以对象的形式传递出去。
resolve({
width: img.width,
height: img.height,
});
};
// 当图片加载失败时,会触发onerror事件。
img.onerror = function() {
// 在加载失败时,通过reject函数返回一个带有错误信息的Error对象。
reject(new Error("getImageSizeByUrl 加载图片失败"));
};
});
},
得到按比例压缩后的宽高
如果最长边大于350,计算缩放比例.如果最长边不大于350,直接返回原始尺寸。这里的350,就是最初始的 cvHeight
和 cvWidth
,数值需要对应!
scaleImage(width, height) {
return new Promise((resolve, reject) => {
// 找到较长的一边
const maxSide = Math.max(width, height);
// 如果最长边大于350,计算缩放比例
if (maxSide > 350) {
const scale = 350 / maxSide;
// 使用缩放比例来调整图片的长宽
const newWidth = Math.round(width * scale);
const newHeight = Math.round(height * scale);
resolve({
width: newWidth,
height: newHeight,
});
} else {
// 如果最长边不大于350,直接返回原始尺寸
resolve({
width,
height,
});
}
});
},
核心添加水印方法
这是一个用于在图片上添加水印并将结果绘制到 canvas 上的异步函数。
它接受一个图片的URL、图片的宽度和高度作为参数,并返回一个Promise对象。
imgToCanvas(url, width, height) {
return new Promise((resolve, reject) => {
// 使用 uni.createCanvasContext 创建一个 Canvas 2D 渲染上下文,参数为在 DOM 元素中定义的 canvas 元素的 id(cid3)。
const ctx = uni.createCanvasContext("cid3");
// 在 Canvas 上使用 drawImage 方法绘制图片,从指定的 URL 加载图片,并设置宽度和高度。
ctx.drawImage(url, 0, 0, width, height);
// 设置水印的大小和样式。
ctx.setFontSize(width / 10); // 设置字体大小为图片宽度的十分之一。
ctx.setFillStyle("rgba(150,150,150,0.2)"); // 设置水印的颜色和透明度。
// 添加两行水印文字。
// 第一行水印,居中显示在图片上半部分。
ctx.fillText(
"长沙市老年人居家",
width / 2 - (width / 10) * 4,
height / 2 - parseInt(width / 10 / 3) - width / 10 / 10 - 10
);
// 第二行水印,居中显示在图片下半部分。
ctx.fillText(
"适老化改造业务专用",
width / 2 - (width / 10) * 4 - width / 10 / 2,
height / 2 + parseInt((width / 10 / 3) * 2) + width / 10 / 10 + 10
);
// 使用 ctx.draw 方法将绘制的内容显示在 Canvas 上。
// 第一个参数为 false,表示此次绘制不清空之前的内容。
// 在绘制完成后,调用 uni.canvasToTempFilePath 将 Canvas 转换为临时文件路径。
ctx.draw(false, () => {
uni.canvasToTempFilePath({
canvasId: "cid3", // 指定要转换的 Canvas 的 id。
fileType: "jpg", // 指定要生成的图片文件类型。
success: (res) => {
// 在转换成功时,通过 resolve 函数返回生成的图片的临时文件路径。
resolve(res.tempFilePath);
},
fail: (err) => {
// 在转换失败时,通过 reject 函数返回错误信息。
reject(err);
},
});
});
});
},
整合图片处理方法
async imageAddWatermarks(url) {
try {
const {
width,
height
} = await this.getImageSizeByUrl(url);
const scaledImage = await this.scaleImage(width, height);
this.$set(this.canvasSize, "cvWidth", scaledImage.width);
this.$set(this.canvasSize, "cvHeight", scaledImage.height);
let waterUrl = await this.imgToCanvas(url, this.canvasSize.cvWidth, this.canvasSize.cvHeight);
// 重置画布大小,否则会有显示不全的问题
this.$set(this.canvasSize, "cvWidth", 350);
this.$set(this.canvasSize, "cvHeight", 350);
return waterUrl;
} catch (error) {
console.error(error);
}
},
上传图片类型检测
这是一个用于检测上传的图片文件格式的函数。它接受一个文件名列表作为参数,并返回一个 Promise 对象。
函数的目的是检查上传的文件是否属于支持的图片格式(.jpg、.jpeg、.png)。
checkImageFiles(filenames) {
// 定义支持的图片文件格式。
const imageExtensions = ['.jpg', '.jpeg', '.png'];
// 创建一个 Promise 数组,对每个文件进行格式检查。
const promises = filenames.map(file => {
return new Promise((resolve, reject) => {
// 获取文件名的小写形式,并提取出文件扩展名。
const fileExtension = file.name.toLowerCase().substring(file.name.lastIndexOf('.'));
// 检查文件扩展名是否在支持的图片格式列表中。
if (imageExtensions.includes(fileExtension)) {
// 如果是支持的格式,通过 resolve 函数返回 true。
resolve(true);
} else {
// 如果不是支持的格式,通过 reject 函数返回错误信息。
reject('格式错误!请上传 jpg 或 png 图片');
}
});
});
// 使用 Promise.all 来等待所有的检查 Promise 完成。
return Promise.all(promises);
},
参考代码
以下代码是配合 uView 框架实现的,用作参考即可
<u-upload :fileList="baseInfoFormData.entrustFileList" @afterRead="afterFileRead" @delete="deletePic" :name="entrustFile.name" multiple :maxCount="entrustFile.maxCount" :previewFullImage="true" width="163" height="102" :deletable="true">
<view class="u-flex u-flex-center u-flex-items-center photo-con">
<u-image width="18" height="18" src="@/static/applicant/upAdd.png"></u-image>
<view class="up-txt">{{ entrustFile.upTxt }}</view>
</view>
</u-upload>
<!-- 添加水印虚拟结点 -->
<view style="width: 0px; height: 0px; overflow: hidden">
<canvas canvas-id="cid3" :style="{
height: `${canvasSize.cvHeight}px`,
width: `${canvasSize.cvWidth}px`,
}"></canvas>
</view>
async afterFileRead(event) {
try {
let lists = [].concat(event.file)
let fileListLen = this.baseInfoFormData.entrustFileList.length
await this.checkImageFiles(lists)
// 添加水印,获得处理后的url
for (const item of lists) {
let waterUrl = await this.imageAddWatermarks(item.url);
item.url = waterUrl
item.thumb = waterUrl
this.baseInfoFormData.entrustFileList.push({
...item,
status: 'uploading',
message: '上传中'
});
}
// 将添加完水印的图片上传至统一存储
for (let i = 0; i < lists.length; i++) {
const imgInfo = await this.uploadFilePromise(lists[i].url);
const imgSrc = imgInfo.imgSrc
const fileId = imgInfo.fileId
let item = this.baseInfoFormData.entrustFileList[fileListLen];
this.baseInfoFormData.entrustFileList.splice(
fileListLen,
1,
Object.assign(item, {
message: "已上传",
status: "success",
url: imgSrc,
thumb: imgSrc,
})
);
this.baseInfoFormData.ahap7744 = fileId
fileListLen++;
}
} catch (e) {
uni.$u.toast(e)
}
},
uploadFilePromise(url) {
return new Promise((resolve, reject) => {
uni.uploadFile({
url: config.basePath + config.interfaceConfig["uploadSLHFile"],
filePath: url,
name: "file",
formData: {
azzx0025: this.getWxuuid(),
businessLevel: 1,
azzx0040: "slh",
azzx0063: "01",
},
success: (res) => {
const result = JSON.parse(res.data);
const fileId = result.data.resultData.fileId;
const imgSrc = this.$url_config.fileBasePath + fileId;
const imgInfo = {
fileId,
imgSrc
}
resolve(imgInfo);
},
});
});
},
async imageAddWatermarks(url) {
try {
const {
width,
height
} = await this.getImageSizeByUrl(url);
const scaledImage = await this.scaleImage(width, height);
this.$set(this.canvasSize, "cvWidth", scaledImage.width);
this.$set(this.canvasSize, "cvHeight", scaledImage.height);
let waterUrl = await this.imgToCanvas(url, this.canvasSize.cvWidth, this.canvasSize.cvHeight);
// 重置画布大小,否则会有显示不全的问题
this.$set(this.canvasSize, "cvWidth", 350);
this.$set(this.canvasSize, "cvHeight", 350);
return waterUrl;
} catch (error) {
console.error(error);
}
},
getImageSizeByUrl(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = url;
img.onload = function() {
resolve({
width: img.width,
height: img.height,
});
};
img.onerror = function() {
reject(new Error("getImageSizeByUrl 加载图片失败"));
};
});
},
// 如果最长边大于350,计算缩放比例.如果最长边不大于350,直接返回原始尺寸
scaleImage(width, height) {
return new Promise((resolve, reject) => {
// 找到较长的一边
const maxSide = Math.max(width, height);
// 如果最长边大于350,计算缩放比例
if (maxSide > 350) {
const scale = 350 / maxSide;
// 使用缩放比例来调整图片的长宽
const newWidth = Math.round(width * scale);
const newHeight = Math.round(height * scale);
resolve({
width: newWidth,
height: newHeight,
});
} else {
// 如果最长边不大于350,直接返回原始尺寸
resolve({
width,
height,
});
}
});
},
imgToCanvas(url, width, height) {
return new Promise((resolve, reject) => {
const ctx = uni.createCanvasContext("cid3"); //在dom元素中定义了一个不显示的canvas元素
ctx.drawImage(url, 0, 0, width, height);
// 设置水印的大小,位置
ctx.setFontSize(width / 10);
ctx.setFillStyle("rgba(150,150,150,0.2)");
// 添加水印
ctx.fillText(
"水印具体内容1",
width / 2 - (width / 10) * 4,
height / 2 - parseInt(width / 10 / 3) - width / 10 / 10 - 10
);
ctx.fillText(
"水印具体内容2",
width / 2 - (width / 10) * 4 - width / 10 / 2,
height / 2 + parseInt((width / 10 / 3) * 2) + width / 10 / 10 + 10
);
// 绘制到canvas上,返回加完水印的 base64 url
ctx.draw(false, () => {
uni.canvasToTempFilePath({
canvasId: "cid3",
fileType: "jpg",
success: (res) => {
resolve(res.tempFilePath);
},
fail: (err) => {
reject(err);
},
});
});
});
},