微信小程序其实自带一个图片压缩的API wx.compressImage,但是这玩意目前感受就是个垃圾。IOS大多数情况下据说还可以,安卓有的时候降低质量压缩后体积反而变大,而且没办法控制其压缩至具体指定的大小,压缩后多大看天意。所以需要使用画布去自己实现一个图片压缩方法。简单来讲原理就是:找个不显示在页面上的画布画上去,再取出,如果体积还是太大,缩小尺寸后再画,再取,递归下去,直到体积满足要求。(所以限制的越小,图片越大,压缩越久,递归次数越多)第一步:新建一个zipPic.js文件(名字你开心就好),里面的代码如下第一步:新建一个zipPic.js文件(名字你开心就好),里面的代码如下
//通过canvas将图片压缩至指定大小

//判断图片大小是否满足需求,limitSize的单位是kb
function imageSizeIsLessLimitSize(imagePath,limitSize,lessCallback,moreCallback){
//获取文件信息
wx.getFileInfo({
filePath:imagePath,
success:(res)=>{

console.log("压缩前图片大小",res.size/1024,'kb');

//如果图片太大了走moreCallback

if(res.size>1024*limitSize){

moreCallback()

}

//图片满足要求了走lessCallback

else{

lessCallback()

}
}
})
}

//将图片画在画布上并获取画好之后的图片的路径
function getCanvasImage(canvasId,imagePath,imageW,imageH,getImgSuccess){
//创建画布内容
const ctx=wx.createCanvasContext(canvasId);
//图片画上去,imageW和imageH是画上去的尺寸,图像和画布间隔都是0
ctx.drawImage(imagePath,0,0,imageW,imageH);
//这里一定要加定时器,给足够的时间去画(所以每次递归最少要耗时200ms,多次递归很耗时!)
ctx.draw(false,setTimeout(function(){
wx.canvasToTempFilePath({

canvasId:canvasId,

x:0,

y:0,

width:imageW,

height:imageH,

quality:1, //最高质量,只通过尺寸放缩去压缩,画的时候都按最高质量来画

success:(res)=>{

getImgSuccess(res.tempFilePath);

}
})
},200));
}

//主函数,默认限制大小1024kb即1mb,drawWidth是绘画区域的大小
//初始值传入为画布自身的边长(我们这是一个正方形的画布)
function getLessLimitSizeImage(canvasId,imagePath,limitSize=1024,drawWidth,callback){
//判断图片尺寸是否满足要求
imageSizeIsLessLimitSize(imagePath,limitSize,
(lessRes)=>{

//满足要求走callback,将压缩后的文件路径返回

callback(imagePath);
},
(moreRes)=>{

//不满足要求需要压缩的时候

wx.getImageInfo({

src:imagePath,

success:(imageInfo)=>{

let maxSide=Math.max(imageInfo.width,imageInfo.height);

let windowW=drawWidth;

let scale=1;

/*

这里的目的是当绘画区域缩小的比图片自身尺寸还要小的时候

取图片长宽的最大值,然后和当前绘画区域计算出需要放缩的比例

然后再画经过放缩后的尺寸,保证画出的一定是一个完整的图片。由于每次递归绘画区域都会缩小,

所以不用担心scale永远都是1绘画尺寸永远不变的情况,只要不满足压缩后体积的要求

就会缩小绘画区域,早晚会有绘画区域小于图片尺寸的情况发生

*/

if(maxSide>windowW){

scale=windowW/maxSide;

}

//trunc是去掉小数

let imageW=Math.trunc(imageInfo.width*scale);

let imageH=Math.trunc(imageInfo.height*scale);

console.log('调用压缩',imageW,imageH);

//图片在规定绘画区域上画并获取新的图片的path

getCanvasImage(canvasId,imagePath,imageW,imageH,

(pressImgPath)=>{

/*

再去检查是否满足要求,始终缩小绘画区域,让图片适配绘画区域

这里乘以0.95是必须的,如果不缩小绘画区域,会出现尺寸比绘画区域小,

而体积比要求压缩体积大的情况出现,就会无穷递归下去,因为scale的值永远是1

但0.95不是固定的,你可以根据需要自己改,0到1之间,越小则绘画区域缩小的越快

但不建议取得太小,绘画区域缩小的太快,压出来的将总是很糊的

*/

getLessLimitSizeImage(canvasId,pressImgPath,limitSize,drawWidth*0.95,callback);

}

)

}

})
}
)
}

export default getLessLimitSizeImage

//通过canvas将图片压缩至指定大小

//判断图片大小是否满足需求,limitSize的单位是kb
function imageSizeIsLessLimitSize(imagePath,limitSize,lessCallback,moreCallback){
//获取文件信息
wx.getFileInfo({
filePath:imagePath,
success:(res)=>{

console.log("压缩前图片大小",res.size/1024,'kb');

//如果图片太大了走moreCallback

if(res.size>1024*limitSize){

moreCallback()

}

//图片满足要求了走lessCallback

else{

lessCallback()

}
}
})
}

//将图片画在画布上并获取画好之后的图片的路径
function getCanvasImage(canvasId,imagePath,imageW,imageH,getImgSuccess){
//创建画布内容
const ctx=wx.createCanvasContext(canvasId);
//图片画上去,imageW和imageH是画上去的尺寸,图像和画布间隔都是0
ctx.drawImage(imagePath,0,0,imageW,imageH);
//这里一定要加定时器,给足够的时间去画(所以每次递归最少要耗时200ms,多次递归很耗时!)
ctx.draw(false,setTimeout(function(){
wx.canvasToTempFilePath({

canvasId:canvasId,

x:0,

y:0,

width:imageW,

height:imageH,

quality:1, //最高质量,只通过尺寸放缩去压缩,画的时候都按最高质量来画

success:(res)=>{

getImgSuccess(res.tempFilePath);

}
})
},200));
}

//主函数,默认限制大小1024kb即1mb,drawWidth是绘画区域的大小
//初始值传入为画布自身的边长(我们这是一个正方形的画布)
function getLessLimitSizeImage(canvasId,imagePath,limitSize=1024,drawWidth,callback){
//判断图片尺寸是否满足要求
imageSizeIsLessLimitSize(imagePath,limitSize,
(lessRes)=>{

//满足要求走callback,将压缩后的文件路径返回

callback(imagePath);
},
(moreRes)=>{

//不满足要求需要压缩的时候

wx.getImageInfo({

src:imagePath,

success:(imageInfo)=>{

let maxSide=Math.max(imageInfo.width,imageInfo.height);

let windowW=drawWidth;

let scale=1;

/*

这里的目的是当绘画区域缩小的比图片自身尺寸还要小的时候

取图片长宽的最大值,然后和当前绘画区域计算出需要放缩的比例

然后再画经过放缩后的尺寸,保证画出的一定是一个完整的图片。由于每次递归绘画区域都会缩小,

所以不用担心scale永远都是1绘画尺寸永远不变的情况,只要不满足压缩后体积的要求

就会缩小绘画区域,早晚会有绘画区域小于图片尺寸的情况发生

*/

if(maxSide>windowW){

scale=windowW/maxSide;

}

//trunc是去掉小数

let imageW=Math.trunc(imageInfo.width*scale);

let imageH=Math.trunc(imageInfo.height*scale);

console.log('调用压缩',imageW,imageH);

//图片在规定绘画区域上画并获取新的图片的path

getCanvasImage(canvasId,imagePath,imageW,imageH,

(pressImgPath)=>{

/*

再去检查是否满足要求,始终缩小绘画区域,让图片适配绘画区域

这里乘以0.95是必须的,如果不缩小绘画区域,会出现尺寸比绘画区域小,

而体积比要求压缩体积大的情况出现,就会无穷递归下去,因为scale的值永远是1

但0.95不是固定的,你可以根据需要自己改,0到1之间,越小则绘画区域缩小的越快

但不建议取得太小,绘画区域缩小的太快,压出来的将总是很糊的

*/

getLessLimitSizeImage(canvasId,pressImgPath,limitSize,drawWidth*0.95,callback);

}

)

}

})
}
)
}

export default getLessLimitSizeImage
好的接下来是使用的方法:好的接下来是使用的方法:在你想压缩图片的js代码所对应的页面中。先放置一个用户看不见的画布。(就是如果我想在index.js中chooseImage再压缩,就需要你在index.html中加上下面的html代码)

style="width: {{cw}}px; height: {{cw}}px;position: absolute; z-index: -1; left: -10000rpx;; top: -10000rpx;"
canvas-id="zipCanvas"
>



style="width: {{cw}}px; height: {{cw}}px;position: absolute; z-index: -1; left: -10000rpx;; top: -10000rpx;"
canvas-id="zipCanvas"
>

其中cw的值我个人建议选择用户屏幕的宽度,如下,在page({…})的data中添加

//画板边长默认是屏幕宽度,正方形画布
cw:wx.getSystemInfoSync().windowWidth,


//画板边长默认是屏幕宽度,正方形画布
cw:wx.getSystemInfoSync().windowWidth,
个人建议画布和绘画区域都是正方形的,毕竟你也不知道要压缩的图片是横向的还是纵向的。然后,引入,不解释
import getLessLimitSizeImage from '../../utils/zipPic'

import getLessLimitSizeImage from '../../utils/zipPic'
在js代码中:
wx.chooseImage({

count:1, //只传一张

sizeType:'original', //原图质量好,然后通过canvas压缩,缩略图压缩就太糊了

sourceType: ['album', 'camera'], // 来源是相册和相机

success:(res)=>{

let canvasId='zipCanvas' //注意这里的id和你在页面中写的html代码的canvas的id要一致

let imagePath=res.tempFilePaths[0];//原图的路径

let limitSize=2048;//大小限制2048kb

let drawWidth=wx.getSystemInfoSync().windowWidth;//初始绘画区域是画布自身的宽度也就是屏幕宽度

wx.showLoading({title:'图片压缩中...',mask:true}) //不需要你可以删掉

getLessLimitSizeImage(canvasId,imagePath,limitSize,drawWidth,(resPath)=>{

wx.hideLoading(); //不需要你可以删掉



//resPath就是压缩后图片的路径,然后想做什么都随你



})

}
})

wx.chooseImage({

count:1, //只传一张

sizeType:'original', //原图质量好,然后通过canvas压缩,缩略图压缩就太糊了

sourceType: ['album', 'camera'], // 来源是相册和相机

success:(res)=>{

let canvasId='zipCanvas' //注意这里的id和你在页面中写的html代码的canvas的id要一致

let imagePath=res.tempFilePaths[0];//原图的路径

let limitSize=2048;//大小限制2048kb

let drawWidth=wx.getSystemInfoSync().windowWidth;//初始绘画区域是画布自身的宽度也就是屏幕宽度

wx.showLoading({title:'图片压缩中...',mask:true}) //不需要你可以删掉

getLessLimitSizeImage(canvasId,imagePath,limitSize,drawWidth,(resPath)=>{

wx.hideLoading(); //不需要你可以删掉



//resPath就是压缩后图片的路径,然后想做什么都随你



})

}
})
补充:补充:

这里代码的主体不是我做的,网上一搜基本都是这个写法,这里是经过项目实践测试后没问题了做的讲解。

这里图片是只选了一张去压缩,如果你需要选多张再挨个压缩那就去写个循环,找个数组存压缩后的结果,网上也有很多内容。

回调函数中有lessRes和moreRes,细心的会发现这两个参数并没有被用到,他们只是个提醒作用,表明当前是less回调还是more回调,如果你不怕弄混删掉了或者自己另外写了两个新方法那都随你。

极限情况下比如说将图片强制压缩至10kb,这个东西我没测试过,不知道会不会有问题。

图片压缩体积的减小不是线性的,给人的感觉有点像二次函数(y=x^2左面那一半),越往后压缩的尺寸变化会越小。当然,这和用户的分辨率,屏幕本身的大小都有关系。

还是那句话,由于每次递归都要给至少200ms的时间去画,所以递归很耗时!!!而不递归进行压缩的话,网络传输又会很耗时!!!所以这个地方怎么取舍,压缩至多大,绘画区域缩小的多快,都要靠你自己的经验去调试。

图片的压缩,长宽比理论上来讲是不变的,但是因为舍弃了小数,可能会有肉眼难以察觉的误差,但是问题不大。如果前端想展示一下压缩后的图片的话,不要忘记在image中加入mode=“aspectFit” 。
这里代码的主体不是我做的,网上一搜基本都是这个写法,这里是经过项目实践测试后没问题了做的讲解。这里图片是只选了一张去压缩,如果你需要选多张再挨个压缩那就去写个循环,找个数组存压缩后的结果,网上也有很多内容。回调函数中有lessRes和moreRes,细心的会发现这两个参数并没有被用到,他们只是个提醒作用,表明当前是less回调还是more回调,如果你不怕弄混删掉了或者自己另外写了两个新方法那都随你。极限情况下比如说将图片强制压缩至10kb,这个东西我没测试过,不知道会不会有问题。图片压缩体积的减小不是线性的,给人的感觉有点像二次函数(y=x^2左面那一半),越往后压缩的尺寸变化会越小。当然,这和用户的分辨率,屏幕本身的大小都有关系。还是那句话,由于每次递归都要给至少200ms的时间去画,所以递归很耗时!!!而不递归进行压缩的话,网络传输又会很耗时!!!所以这个地方怎么取舍,压缩至多大,绘画区域缩小的多快,都要靠你自己的经验去调试。图片的压缩,长宽比理论上来讲是不变的,但是因为舍弃了小数,可能会有肉眼难以察觉的误差,但是问题不大。如果前端想展示一下压缩后的图片的话,不要忘记在image中加入mode=“aspectFit” 。