前言:本文记录作者在vue项目中因图片上传限制及后台保存图片过大的问题,因此在图片上传到后台前做一个图片压缩的功能。如果觉得作者写的不错,希望得到您的点赞/收藏/支持,如果有不同意见,欢迎下方评论区留言。
注:
1、此方法以满足大致的功能需求,但是还有一个小问题!!!下面在解释为啥,哈哈哈
2、以下代码注释已经很清晰了。
index.js
/**
* 将图片压缩为对应尺寸
* @param {Object} options
* @param {String} options.img 图片的url或者base64数据
* @param {Number} options.width 目标图片的宽度
* @param {Number} options.height 目标图片的高度
* @param {Number} options.quality 生成目标图片质量
* @param {String} options.fit 图片压缩填充模式默认 scale:按比例缩放,可选 fill:按使用目标尺寸
* @param {String} options.type 图片压缩类型默认 jpg,可选 png
* @param {Number} options.rotate 图片旋转,由于手机拍照的角度和我们使用的头像不一致,需要旋转 默认0 仅支持 90 180 -90
* @returns {Promise} then {width,height,img}
*/
export function pictureCompress (options) {
return new Promise((resolve, reject) => {
if (!options.img) {
reject(new Error('need img'))
return
}
let imgSrc = options.img,
width = options.width || 640,
height = options.height || 640,
type = options.type || 'jpg',
quality = options.quality || 0.92,
fit = options.fit || 'scale',
rotate = options.rotate || 0
if (width <= 0 || height <= 0) {
reject(new Error('dist width or height need > 0'))
return
}
if (!/jpg|png|jpeg/.test(type)) {
reject(new Error('type need jpg or png!'))
return
}
if (rotate !== 90 && rotate !== -90 && rotate !== 0 && rotate !== 180) {
reject(new Error('rotate mast be 0 90 -90 180!'))
return
}
let changeWidthAndHeight = rotate === 90 || rotate === -90
let image = new Image()
image.src = imgSrc
image.onload = function () {
let distSize = getDistSize({
width: changeWidthAndHeight ? this.naturalHeight : this.naturalWidth,
height: changeWidthAndHeight ? this.naturalWidth : this.naturalHeight
}, {
width: changeWidthAndHeight ? height : width,
height: changeWidthAndHeight ? width : height
}, fit)
let imgData = compress(this, distSize.width, distSize.height, type, quality, rotate)
resolve({
width: distSize.width,
height: distSize.height,
img: imgData
})
}
image.onerror = function (err) {
reject(err)
}
})
}
/**
* 将图片转换为固定尺寸的
* @param {Image} img 图片数据
* @param {Number} width 转换之后的图片宽度
* @param {Number} height 转换之后的图片高度
* @param {String} type base64的图片类型 jpg png
* @param {Number} quality 转换之后的图片质量
*/
export function compress (img, width, height, type, quality, rotate) {
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d')
let types = {
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'png': 'image/png'
}
canvas.width = width
canvas.height = height
ctx.drawImage(img, 0, 0, width, height)
return canvas.toDataURL(types[type], quality)
}
/**
* 选择源尺寸与目标尺寸比例中较小的那个,保证图片可以完全显示
* 最大值不超过1,如果图片源尺寸小于目标尺寸,则不做处理,返回图片原尺寸
* @param {Object} source 源图片的宽高
* @param {Object} dist 目标图片的宽高
*/
export function getDistSize (source, dist, fit) {
if (fit === 'fill') return dist
let scale = Math.min(dist.width / source.width, dist.height / source.height, 1)
return {
width: Math.round(source.width * scale),
height: Math.round(source.height * scale)
}
}
// 将base64转换为file文件
export function dataURLtoFile (dataurl, filename) {
let arr = dataurl.split(',')
let mime = arr[0].match(/:(.*?);/)[1]
let bstr = atob(arr[1])
let n = bstr.length
let u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new File([u8arr], filename, {
type: mime })
}
下面是调用该方法:index.vue
import {
pictureCompress , dataURLtoFile } from "./index.js"
afterRead (file) {
pictureCompress({
img: file.content, //上传的图片的base64
}).then(res => {
//console.log(res) 可自行输出res返回的参数数据
//此处为将压缩过后的base64转换成file文件流格式
console.log(dataURLtoFile(res.img,file.file.name))
})
}
到这里大家以为就能使用了吗?答案是可以的。但是!注意了canvas进行移动端手机照片上传时,发现IOS手机上传照片会逆时针旋转90度的问题。那么我们来看以下的代码如何解决的吧~~
解决这个问题的思路是:获取到照片拍摄的方向角,对旋转后的IOS照片进行角度旋转修正。
这里主要用到Orientation属性(最常出现的几个参数)。说明如下:
旋转角度 | 参数 |
---|---|
0° | 1 |
180° | 3 |
顺时针90° | 6 |
逆时针90° | 8 |
1、安装Exif.js
cnpm install exif-js --save
2、引入并使用
import EXIF from 'exif-js'
let orientation
EXIF.getData(img, function () {
orientation = Number(EXIF.getTag(this, 'Orientation'))
})
那么到现在,就已经拿到了图片旋转角度的参数。就可以对上面初步封装的方法进行一个修改。
注:
1、因为是针对IOS机型的修改,所以增加了一层判断条件。详情看代码~
index.js
import EXIF from 'exif-js'
/**
* 将图片压缩为对应尺寸
* @param {Object} options
* @param {String} options.img 图片的url或者base64数据
* @param {Number} options.width 目标图片的宽度
* @param {Number} options.height 目标图片的高度
* @param {Number} options.quality 生成目标图片质量
* @param {String} options.fit 图片压缩填充模式默认 scale:按比例缩放,可选 fill:按使用目标尺寸
* @param {String} options.type 图片压缩类型默认 jpg,可选 png
* @param {Number} options.rotate 图片旋转,由于手机拍照的角度和我们使用的头像不一致,需要旋转 默认0 仅支持 90 180 -90
* @returns {Promise} then {width,height,img}
*/
export function pictureCompress (options) {
return new Promise((resolve, reject) => {
if (!options.img) {
reject(new Error('need img'))
return
}
let imgSrc = options.img,
width = options.width || 640,
height = options.height || 640,
type = options.type || 'jpg',
quality = options.quality || 0.92,
fit = options.fit || 'scale',
rotate = options.rotate || 0
if (width <= 0 || height <= 0) {
reject(new Error('dist width or height need > 0'))
return
}
if (!/jpg|png|jpeg/.test(type)) {
reject(new Error('type need jpg or png!'))
return
}
if (rotate !== 90 && rotate !== -90 && rotate !== 0 && rotate !== 180) {
reject(new Error('rotate mast be 0 90 -90 180!'))
return
}
let changeWidthAndHeight = rotate === 90 || rotate === -90
let image = new Image()
image.src = imgSrc
image.onload = function () {
let distSize = getDistSize({
width: changeWidthAndHeight ? this.naturalHeight : this.naturalWidth,
height: changeWidthAndHeight ? this.naturalWidth : this.naturalHeight
}, {
width: changeWidthAndHeight ? height : width,
height: changeWidthAndHeight ? width : height
}, fit)
let imgData = compress(this, distSize.width, distSize.height, type, quality, rotate)
resolve({
width: distSize.width,
height: distSize.height,
img: imgData
})
}
image.onerror = function (err) {
reject(err)
}
})
}
/**
* 将图片转换为固定尺寸的
* @param {Image} img 图片数据
* @param {Number} width 转换之后的图片宽度
* @param {Number} height 转换之后的图片高度
* @param {String} type base64的图片类型 jpg png
* @param {Number} quality 转换之后的图片质量
*/
export function compress (img, width, height, type, quality, rotate) {
var canvas = document.createElement('canvas')
var ctx = canvas.getContext('2d')
let types = {
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'png': 'image/png'
}
canvas.width = width
canvas.height = height
let orientation
EXIF.getData(img, function () {
orientation = Number(EXIF.getTag(this, 'Orientation'))
})
// 针对IOS系统上传照片会旋转
if (!!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)) {
if (orientation != "" && orientation != 1) {
switch (orientation) {
case 3:// 需要180度旋转
rotateImg(img, 'right2', canvas, width, height);
break;
case 6:// 需要顺时针(向左)90度旋转
rotateImg(img, 'left', canvas, width, height);
break;
case 8:// 需要逆时针(向右)90度旋转
rotateImg(img, 'right', canvas, width, height);
break;
default:
ctx.drawImage(img, 0, 0, width, height);
break;
}
} else {
ctx.drawImage(img, 0, 0, width, height)
}
} else {
ctx.drawImage(img, 0, 0, width, height)
}
return canvas.toDataURL(types[type], quality)
}
export function rotateImg (img, direction, canvas, width, height) {
var min_step = 0;
var max_step = 3;
if (img == null) {
return;
}
var step = 2;
if (step == null) {
step = min_step;
}
if (direction == 'right') {
step++;
step > max_step && (step = min_step);
} else if (direction == 'right2') {
step = 2;
} else {
step--;
step < min_step && (step = max_step);
}
var degree = step * 90 * Math.PI / 180;
var ctx = canvas.getContext('2d');
switch (step) {
case 0:
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
break;
case 1:
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, 0, -height, width, height);
break;
case 2:
canvas.width = width;
canvas.height = height;
ctx.rotate(degree);
ctx.drawImage(img, -width, -height, width, height);
break;
case 3:
canvas.width = height;
canvas.height = width;
ctx.rotate(degree);
ctx.drawImage(img, -width, 0, width, height);
break;
}
}
/**
* 选择源尺寸与目标尺寸比例中较小的那个,保证图片可以完全显示
* 最大值不超过1,如果图片源尺寸小于目标尺寸,则不做处理,返回图片原尺寸
* @param {Object} source 源图片的宽高
* @param {Object} dist 目标图片的宽高
*/
export function getDistSize (source, dist, fit) {
if (fit === 'fill') return dist
let scale = Math.min(dist.width / source.width, dist.height / source.height, 1)
return {
width: Math.round(source.width * scale),
height: Math.round(source.height * scale)
}
}
// 将base64转换为file文件
export function dataURLtoFile (dataurl, filename) {
let arr = dataurl.split(',')
let mime = arr[0].match(/:(.*?);/)[1]
let bstr = atob(arr[1])
let n = bstr.length
let u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new File([u8arr], filename, {
type: mime })
}
index.vue
import {
pictureCompress , dataURLtoFile } from "./index.js"
afterRead (file) {
pictureCompress({
img: file.content, //上传的图片的base64
}).then(res => {
//console.log(res) 可自行输出res返回的参数数据
//此处为将压缩过后的base64转换成file文件流格式
console.log(dataURLtoFile(res.img,file.file.name))
//下面就是请求接口上传压缩后的图片代码位置↓
})
}
至此,图片上传前的压缩功能就写好了!
最后来复习一下:如果觉得作者写的不错,希望得到您的点赞/收藏/支持,如果有不同意见,欢迎下方评论区留言。