上传按钮UI同构
xml
<!-- 微信环境(小程序/微信浏览器) -->
<div class="upload-button" v-if="is_WeChat" @click="chooseWeChatImage">上传图片</div>
<!-- 原生H5(pc浏览器/手机浏览器),此处用的vant组件,基本同input上传。 -->
<van-uploader v-else :after-read="chooseRawImage" accept="image/*"
ref="h5Upload">
<div class="upload-button" >上传图片</div>
</van-uploader>
微信API & H5原生上传流程
注:调用wx.chooseImage 需要H5正确配置jssdk
javascript
<!-- 微信API获取图片 -->
chooseWeChatImage() {
const self = this
// 1 选择图片
this.$wx.chooseImage({
count: 1,
sizeType: ["compressed"],
sourceType: ["album", "camera"], // 可以指定来源是相册还是相机,默认二者都有
success: function(res) {
let id = res.localIds[0]
self.$loading.show("识别中...")
// 2 通过图片的localID 获取 图片的base64数据
self.$wx.getLocalImgData({
localId: id,
success: function(res) {
// 3 base64兼容处理(IOS与安卓兼容)
let localData = res.localData
if (localData.indexOf("data:image") != 0) {
localData = "data:image/jpeg;base64," + localData
}
localData = localData.replace(/\r|\n/g, "").replace("data:image/jgp", "data:image/jpeg")
// 4 base64 转 File
const file = base64ToFile(localData)
console.log("base64ToFile", file)
// 5 上传图片(同时返回 File 和 base64,方便使用 )
self.uploadImgFile({ file: file, content: localData, compress: false })
}
})
},
fail(err) {
console.log("err", err)
}
})
}
/**
* 原生获取图片
* @param {Object} result - {content,file,message,status}
**/
chooseRawImage(result){
this.uploadImgFile({...result, compress: true})
}
/**
* 上传图片到服务器获取图片链接
* @param {Object} result - file: 文件;content:base64
*/
async uploadImgFile(result) {
if (result.compress) {
// 因上传一般需要传file故仅压缩file,有需要可自行转base64
const { file } = await compressImg(result.file, 0.7)
result.file = file
}
const formData = new FormData()
formData.append("file", res.file)
try {
const res = await uploadImgFileApi(formData)
} catch (error) {
this.$loading.hide()
error && console.log("上传图片异常", error)
}
}
工具方法
1. base64 转 File
javascript
/**
* base64转文件
* @param {string} base64 - 图片base64字符串
* @returns {File | null} file
*/
export function base64ToFile(base64) {
try {
let bstr = null
let n = null
let mime = base64.split(",")[0].match(/:(.*?);/)[1] //mime类型 image/png
let u8arr = null
if (base64.indexOf(",") > 0) {
// ios兼容
let arr = base64.split(",")
bstr = atob(arr[1])
} else {
bstr = atob(base64)
}
n = bstr.length
u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
// e_worker_ 表示业务标识,可自行修改
let file = new File([u8arr], `e_worker_${new Date().getTime()}.${mime.split("/")[1]}`, { type: mime })
return file
} catch (err) {
console.error("异常", err)
return null
}
}
2. 图片压缩方法
ini
/**
* 压缩图片方法
* @param {file} file 文件
* @param {Number} quality 图片质量(取值0-1之间默认0.92)
*/
export const compressImg = function(file, quality) {
var qualitys = 0.52
console.log(parseInt((file.size / 1024).toFixed(2)))
if (parseInt((file.size / 1024).toFixed(2)) < 1024) {
qualitys = 0.85
}
if (5 * 1024 < parseInt((file.size / 1024).toFixed(2))) {
qualitys = 0.92
}
if (quality) {
qualitys = quality
}
if (file[0]) {
return Promise.all(Array.from(file).map(e => compressImg(e, qualitys))) // 如果是 file 数组返回 Promise 数组
} else {
return new Promise(resolve => {
console.log(file)
if ((file.size / 1024).toFixed(2) < 300) {
resolve({
file: file
})
} else {
const reader = new FileReader() // 创建 FileReader
reader.onload = ({ target: { result: src } }) => {
const image = new Image() // 创建 img 元素
image.onload = async () => {
const canvas = document.createElement("canvas") // 创建 canvas 元素
const context = canvas.getContext("2d")
var targetWidth = image.width
var targetHeight = image.height
var originWidth = image.width
var originHeight = image.height
if (
1 * 1024 <= parseInt((file.size / 1024).toFixed(2)) &&
parseInt((file.size / 1024).toFixed(2)) <= 10 * 1024
) {
var maxWidth = 1600
var maxHeight = 1600
targetWidth = originWidth
targetHeight = originHeight
// 图片尺寸超过的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更宽,按照宽度限定尺寸
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
}
if (
10 * 1024 <= parseInt((file.size / 1024).toFixed(2)) &&
parseInt((file.size / 1024).toFixed(2)) <= 20 * 1024
) {
maxWidth = 1400
maxHeight = 1400
targetWidth = originWidth
targetHeight = originHeight
// 图片尺寸超过的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更宽,按照宽度限定尺寸
targetWidth = maxWidth
targetHeight = Math.round(maxWidth * (originHeight / originWidth))
} else {
targetHeight = maxHeight
targetWidth = Math.round(maxHeight * (originWidth / originHeight))
}
}
}
canvas.width = targetWidth
canvas.height = targetHeight
context.clearRect(0, 0, targetWidth, targetHeight)
context.drawImage(image, 0, 0, targetWidth, targetHeight) // 绘制 canvas
const canvasURL = canvas.toDataURL("image/jpeg", qualitys)
const buffer = atob(canvasURL.split(",")[1])
let length = buffer.length
const bufferArray = new Uint8Array(new ArrayBuffer(length))
while (length--) {
bufferArray[length] = buffer.charCodeAt(length)
}
const miniFile = new File([bufferArray], file.name, {
type: "image/jpeg"
})
console.log({
file: miniFile,
origin: file,
beforeSrc: src,
afterSrc: canvasURL,
beforeKB: Number((file.size / 1024).toFixed(2)),
afterKB: Number((miniFile.size / 1024).toFixed(2)),
qualitys: qualitys
})
resolve({
file: miniFile,
origin: file,
beforeSrc: src,
afterSrc: canvasURL,
beforeKB: Number((file.size / 1024).toFixed(2)),
afterKB: Number((miniFile.size / 1024).toFixed(2))
})
}
image.src = src
}
reader.readAsDataURL(file)
}
})
}
}
问题与提示
1. wx.chooseImage无法获取原图的问题。
wx.chooseImage 方法虽然提供了获取原图的选项(sizeType),但安卓和IOS仍无法获取到原图。[1]
使用原生上传可以拿到原始大小的文件。
2. 两种方式中,都需要压缩吗?
- wx.chooseImage 获取的图片本身文件体积较小、质量也尚可,所以可以不用压缩;
- H5获取的是原始图片,文件体积较大建议压缩图片。
3. 部分情况下,ios手机用原生上传图片为什么依然不是原图?
- 可能的原因:ios的heic格式图片可能在上传时系统做了转换处理。