微信小程序

微信小程序使用canvas进行按需图片列表压缩

2020-08-20  本文已影响0人  羞羞的小桔

概述

​ 在微信小程序中使用canvas来对图片进行压缩的例子网上已经有很多了,但是网上的例子都只能对单张图片进行压缩。由于官方接口wx.chooseImage()在实际生产常常被用于进行多张图片的选择,所以我们需要一个方法来对图片列表进行压缩。

​ 可是已经有了压缩单张图片的方法,那么循环调用它不就能对图片列表进行压缩了吗?实际上不是。由于在微信小程序中只能够使用wx.createCanvasContext()接口通过wxml中定义的canvas-id来获取canvas对象,继而使用这个对象来进行绘图。因此当循环调用图片压缩方法时,就会导致每次循环都使用同一个canvas对象绘图、导出图片,并且由于canvas的绘图方法是异步进行的,因此最终获取到的压缩后的图片列表就会出现紊乱(亲测)。除非你愿意在wxml中定义多个id不同的canvas。

​ 循环压缩图片行不通,因此本文介绍了使用递归的方法来对图片列表进行压缩。由于使用递归来实现列表的循环压缩,因此能够避免canvas异步绘图方法带来的影响。

其实说到递归,思路就很明显了,不过其中也有一些细节和坑值得记录一下。

具体实现

实现步骤

  1. 使用wx.chooseImage()接口选择图片,接口返回图片地址列表。
  2. 递归调用图片压缩方法。
  3. 图片压缩方法中使用wx.getImageInfo()接口获取图片长宽信息。
  4. 根据图片长宽信息计算图片大小,并根据目标图片大小对图片的长宽进行裁剪,以达到压缩图片大小的目的。
  5. 根据canvas-id获取canvas绘图上下文,绘制最终图片后将画布内容导出为图片。

代码实现

wxml代码


<canvas canvas-id="canvas" style="width:{{cWidth}}px;height:{{cHeight}}px;position: absolute;left:-1000px;top:-1000px;"></canvas>

js代码


data: { cWidth: 0, cHeight: 0, pics: []}

chooseImg() {
    // 限制最多选择5张图片
    const count = 5 - this.data.pics.length;
    wx.chooseImage({
        count: count,
        // 在小程序的api中说明设置sizeType=compressed后会返回压缩后的图片,但测试发现,只有图片大小超出某个范围之后才会对图片进行压缩处理
        sizeType: ['original', 'compressed'],
        sourceType: ['album', 'camera'],
        success: res => {
            const urls = res.tempFilePaths;
            // 参数分别为:图片url数组、wxml中设置的canvasId、递归所需index、设置canvas长宽的方法、处理压缩后图片的回调方法
            util.cutBase64(urls, 'canvasId', 0, this.setCanvas, this.setPics);
        }
    })
},
/**
 * 通过这个方法设置canvas的长宽,设置后调用回调方法。
 * 如果不使用回调方法,而是直接设置canvas长宽后就进行下一步处理,最终得到的图片大小可能会有跟预期有所差异
 */
setCanvas(width, height, callback) {
    this.setData({
        cWidth: width,
        cHeight: height
    }, callback);
},
/**
 * 对压缩后的图片进行处理
 */
setPics(url) {
    // 这样处理是因为在我的使用过程中需要得到图片的base64数据,也可以根据实际需求对url做出不同的处理
    wx.getFileSystemManager().readFile({
        filePath: url, //选择图片返回的相对路径
        encoding: 'base64', //编码格式
        success: res => {
            const base64 = 'data:image/png;base64,' + res.data;
            // 打印出压缩后的图片大小
            console.log(base64.length);
            this.setData({
                pics: this.data.pics.push(base64);
            });
        }
    })
}

Util工具类:cutBase64()

const cutBase64 = (urls, canvasId, index, setCanvas, callback) => {
    if (index == urls.length) {
        return;
    }
    // 根据urls及index获取图片信息
    wx.getImageInfo({
        src: urls[index],
        success: (res) => {
            // 设置初始裁剪比例,当图片大小大于设置阈值时,则对图片长宽按次比例进行裁剪
            let ratio = 2;
            let canvasWidth = res.width;
            let canvasHeight = res.height;
            // 根据比例对图片长宽进行裁剪,若裁剪后图片大小依然大于设置阈值时,则提高裁剪比例继续对图片进行裁剪
            // 此处假定需要将图片裁剪至200kb以内,因此 阈值 = 200 * 1024 = 204800
            while (canvasWidth * canvasHeight > 204800) {
                canvasWidth = Math.trunc(res.width / ratio);
                canvasHeight = Math.trunc(res.height / ratio);
                ratio++;
            }
            // 调用setCanvas()方法设置canvas的长宽,设置完成后开始绘图,否则可能会出现"fail canvas is empty"的错误
            setCanvas(canvasWidth, canvasHeight, () => {
                // 获取canvasId对应的绘图上下文对象
                let ctx = wx.createCanvasContext(canvasId);
                ctx.drawImage(urls[index], 0, 0, canvasWidth, canvasHeight);
                // draw()的第一个参数为false表示不保留canvas上一次的绘制结果
                // 这里设置100毫秒的延迟是为了在canvas绘图完成后再获取图片信息,否则获取到的图片可能是空白
                ctx.draw(false, setTimeout(() => {
                    // 获取canvas绘制内容并转为图片
                    wx.canvasToTempFilePath({
                        canvasId: canvasId,
                        destWidth: canvasWidth,
                        destHeight: canvasHeight,
                        fileType: 'jpg', // 可选的参数有:jpg、png
                        success: res => {
                            // 图片导出成功后调用回调
                            callback(res.tempFilePath);
                            // 进行下一次递归,由于此时canvas的绘制与图片的导出都已经结束了,因此不会对canvas后续的绘制、导出产生影响
                            cutBase64(urls, canvasId, ++index, setCanvas, callback);
                        }
                    })
                }, 100));
            });
        }
    })
}
上一篇 下一篇

猜你喜欢

热点阅读