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

概述

​ 在微信小程序中使用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代码

  • 在wxml文件末尾定义canvas组件,通过设置绝对定位使canvas离屏显示。
  • 设置canvas-id来获取canvas绘图上下文。

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

js代码

  • 在data中定义cWidth和cHeight属性用以动态设置canvas的长宽。
  • pics数组用以存放和显示压缩后的图片列表。

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

  • wx.chooseImage()的处理。在页面的js文件中调用chooseImage()方法选择图片,继而将选择的图片传递到cutBase64()方法中进行压缩处理。
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));
            });
        }
    })
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351