小程序图片上传压缩

文章介绍了小程序中处理图片压缩的三种接口:wx.chooseMedia、wx.canvasToTempFilePath和wx.compressImage,分析了它们的优缺点及适用场景,并提供了结合使用这些接口来优化图片压缩的方法,以适应不同系统和图片尺寸,确保用户上传图片的顺利和加载速度。
摘要由CSDN通过智能技术生成

上传图片是小程序常见的功能,例如点评类小程序邀请用户分享照片、电商类小程序要求商家上传商品照片。

伴随着照片像素越来越高,图片体积越来越大,小程序开发者需要压缩图片,否则将导致用户上传图片失败或加载时间过长等影响体验的情况。

小程序提供 wx.chooseMedia、wx.canvasToTempFilePath、wx.compressImage 3 个图片类接口,便于开发者在不同应用场景下处理图片。除此以外,这 3 个接口的巧妙结合能够满足更多元化的图片压缩需求。下面就来看看怎样使用吧!

wx.chooseMedia

wx.chooseMedia 支持在使用小程序过程中拍摄或从手机相册选择图片或视频,其 sizeType 属性支持是否上传缩略图。该接口应用简便,接入即可实现压缩图片效果,省时省力。

wx.chooseMedia({
  count: 9,
  mediaType: ['image'], // 只允许选择图片
  sourceType: ['album', 'camera'],  // 可以拍摄或从相册中选择
  sizeType:['compressed'],  // 选择压缩图
  camera: 'back', // 后置摄像头
  success(res) {
    console.log(res)
  }
});

然而,该接口在压缩图片方面也有一定的限制:

  • 无法指定压缩质量

  • 部分安卓机型存在压缩失效的情况

  • iOS 和安卓的压缩机制不同,需要进行合理兼容

wx.canvasToTempFilePath

开发者可以通过控制 Canvas.createImage 绘制图片到 Canvas,然后利用 wx.canvasToTempFilePath 接口转换成图片。

这种方法能够高效控制图片宽高尺寸以及压缩质量,非常适用于有图片要求的场景。

wx.canvasToTempFilePath({
  width: 50, // 画布区域的宽度
  height: 50,  // 画布区域的高度
  destWidth: 100,  // 输出图片的宽度
  destHeight: 100,  // 输出图片的高度
  canvasId: 'myCanvas',
  quality: 1,  // 图片质量0-1
  success(res) {
    console.log(res.tempFilePath)
  }
});

但是这种方式也会存在一定的限制:

  • iOS 和安卓的压缩机制不同,需要进行合理兼容

  • 通过 Canvas 转换的图片存在略微色差

wx.compressImage

开发者可以调用wx.compressImage 接口直接压缩图片,而且支持选择压缩质量,不限制图片宽高尺寸,非常适用于处理特殊大小的图片。

wx.compressImage({
  src: '', // 图片路径
  quality: 80 // 压缩质量 0-100
});

同时这种方式也需要考虑不同系统的压缩差异:

  • 在压缩到极限值时,iOS 压缩图画质不会随着压缩质量变小而变化

  • 在压缩质量小于 1 时,安卓系统输出的画质将不再变小

多方式结合处理

回顾常见的小程序业务场景,图片处理主要聚焦于用户上传图片、列表展示这 2 个环节,可以结合以上 3 个接口实现最佳图片处理方式,既能够利用接口自带的压缩功能,省时省力;又能够解决图片太大造成的压缩难题。

  1. 判断系统类型

判断当前系统是 iOS 系统还是安卓系统

function isIOS(){
	return wx.getSystemInfo().then(res => {
		return /IOS/ig.test(res.system);
	});
}
  1. 根据系统选择上传方式

iOS 系统:设置 sizeType 为 [‘compressed’],利用 iOS 压缩体系自动压缩

安卓系统:设置 sizeType 为 [‘original’, ‘compressed’],让用户自主选择上传原图或压缩图。

这种方式一方面利用接口自带的压缩能力; 另一方面如果图片宽高大于安卓能清晰压缩的值(例如40000),用户会预览到比较模糊的照片而选择上传原图

  1. 验证大小,手动压缩

当用户选择图片后,wx.chooseMedia 返回的 tempFiles 显示对应图片的大小。如果该图片大小大于限制值,则进行手动压缩。

  1. 根据宽高选择压缩方式

通过 wx.getImageInfo 获取图片的宽高:

如果宽度或高度大于 4096,调用 wx.compressImage 强制压缩

如果宽度和高度都小于 4096,绘制 Canvas 实现压缩,设置压缩基础宽高为 1280

代码如下:

// compressImage.js
/**
 * @param {object} img 包含path:图片的path,size:图片的大小
 * @param {object} canvas canvas对象
 * @param {number} fileLimit 文件大小限制
 * @returns {Promise} 返回Promise对象
 */
function _compressImage(img, canvas, fileLimit) {
  return wx.getSystemInfo().then(res => {
    let {
      // 设备像素比
      pixelRatio,
      // 设备品牌
      system
    } = res;
    // 是否是IOS系统
    let isIOS = /(ios)/ig.test(system);
    // 文件限制
    fileLimit = fileLimit || 2 * 1024 * 1024;
    // 基础大小
    let baseSize = 1280;
    // 大于文件限制,手动压缩
    if (img.size > fileLimit) {
      return compressImg({src:img.path, size:img.size, canvas, baseSize, isIOS, pixelRatio}).then(response => {
        return Promise.resolve(response);
      });
    }
    return Promise.resolve(img.path);
  });
}

/**
 * @description 根据图片的大小选择压缩的方式
 * @param {string} src 图片的path
 * @param {number} size 图片的大小
 * @param {object} canvas canvas对象
 * @param {number} baseSize 基础尺寸
 * @param {boolean} isIOS 是否是IOS系统 
 * @returns {Promise} 返回Promise对象
 */
function compressImg({src, size, canvas, baseSize, isIOS, pixelRatio}) {
  return new Promise((resolve, reject) => {
    wx.getImageInfo({
      src
    }).then(res => {
      let imgWidth = res.width;
      let imgHeight = res.height;
      if (imgWidth <= 4096 && imgHeight <= 4096) {
        // 小于4096使用canvas压缩
        canvasToImage({src, size, imgWidth, imgHeight, canvas, baseSize, isIOS, pixelRatio}).then(response => {
          resolve(response);
        });
      } else {
        // 超过4096使用强制压缩
        compressImage(src, size, isIOS).then(response => {
          resolve(response);
        });
      }
    }).catch(err => {
      // 使用强制压缩
      compressImage(src, size, isIOS).then(response => {
        resolve(response);
      });
    });
  });
}

/**
 * @description 使用wx.compressImage压缩图片
 * @param {string} src 图片的path
 * @param {number} size 图片的大小
 * @param {boolean} isIOS 是否是IOS系统
 * @returns {Promise} 返回Promise对象
 */
function compressImage(src, size, isIOS) {
  return new Promise((resolve, reject) => {
    let quality = 100;
    if (isIOS) {
      quality = 0.1;
    } else {
      let temp = 30 - (size / 1024 / 1024);
      quality = temp < 10 ? 10 : temp;
    }
    wx.compressImage({
      src,
      quality,
      success: (res) => {
        resolve(res.tempFilePath);
      },
      fail: () => {
        // 压缩失败返回传递的图片src
        resolve(src);
      }
    });
  });
}

/**
 * @description 使用canvans压缩图片
 * @param {string} src 图片的path
 * @param {number} size 图片的大小
 * @param {number} imgWidth 图片的宽度
 * @param {number} imgHeight 图片的高度
 * @param {object} canvas canvas对象
 * @param {number} baseSize 基础尺寸
 * @param {boolean} isIOS 是否是IOS系统
 * @param {number} pixelRatio 设备像素比
 * @returns {Promise} 返回Promise对象
 */
function canvasToImage({src, size, imgWidth, imgHeight, canvas, baseSize, isIOS, pixelRatio}) {
  return new Promise((resolve, reject) => {
    if (!canvas) {
      compressImage(src, size).then(res => {
        resolve(res);
      });
      return;
    }
    // 设置canvas宽度和高度
    let canvasWidth = 0;
    let canvasHeight = 0;
    let quality = 1;
    // 图片的宽度和高度都小于baseSize,宽高不变
    if (imgWidth <= baseSize && imgHeight <= baseSize) {
      canvasWidth = imgWidth;
      canvasHeight = imgHeight;
      quality = 0.3;
    } else {
      let compareFlag = true;
      // 图片的一边大于baseSize,宽高不变
      if (pixelRatio > 2 && (imgWidth > baseSize || imgHeight > baseSize) && (imgWidth < baseSize || imgHeight < baseSize)) {
        canvasWidth = imgWidth;
        canvasHeight = imgHeight;
        quality = 0.3;
      } else {
        // 按照原图的宽高比压缩
        compareFlag = pixelRatio > 2 ? (imgWidth > imgHeight) : (imgWidth > imgHeight);
        // 宽比高大,宽按基准比例缩放,高设置为基准值,高比宽大,高按基准比例缩放,宽设置为基准值。
        canvasWidth = compareFlag ? parseInt(imgWidth / (imgHeight / baseSize)) : baseSize;
        canvasHeight = compareFlag ? baseSize : parseInt(imgHeight / (imgwidth / baseSize));
        quality = 0.9;
      }
    }
    let pic = canvas.createImage();
    pic.src = src;
    pic.onerror = function () {
      // 加载失败使用强制压缩
      compressImage(src, size, isIOS).then(response => {
        resolve(response);
      });
    }
    pic.onload = function () {
      // 获取绘画上下文
      let ctx = canvas.getContext('2d');
      ctx.clearRect(0, 0, canvasWidth, canvasHeight);
      ctx.drawImage(pic, 0, 0, canvasWidth, canvasHeight);
      // 导出图片
      wx.canvasToTempFilePath({
        canvas,
        width: canvasWidth,
        height: canvasHeight,
        destHeight: canvasHeight,
        destWidth: canvasWidth,
        fileType:'jpg',
        quality,
        success: (res) => {
          resolve(res.tempFilePath);
        },
        fail: (err) => {
          // 压缩失败使用强制压缩
          compressImage(src, size, isIOS).then(response => {
            resolve(response);
          });
        }
      });
    }
  });
}

/**
 * @description 循环压缩图片
 * @param {object} img 包含path:图片的path,size:图片的大小
 * @param {object} canvas canvas对象
 * @param {number} fileLimit 文件大小限制
 * @returns {Promise} 返回Promise对象
 */
async function cycleCompressImg(img, canvas, fileLimit) {
  let fileSystemManager = wx.getFileSystemManager();

  function getFileInfoPromise(src) {
    return new Promise((resolve, reject) => {
      fileSystemManager.getFileInfo({
        filePath: src,
        success: (res) => {
          resolve(res);
        },
        fail: (err) => {
          reject(err);
        }
      });
    });
  }
  let size = await getFileInfoPromise(img.path).size;
  let path = img.path;
  while (size > fileLimit) {
    path = await _compressImage(img, canvas, fileLimit);
  }
  return path;
}

module.exports = {
  _compressImage,
  cycleCompressImg
};

使用

在需要使用的页面中,引入上面的代码。
由于在压缩中使用canvas因此需要在wxml文件中添加canvas元素

<view class="upload">
  <view class="imageList">
    <block wx:if="{{uploadedFilePaths && uploadedFilePaths.length}}">
      <view class="imageList_item" wx:for="{{uploadedFilePaths}}" wx:key="index">
        <image src="{{item}}" class="upload_file"></image>
        <view class="close" bindtap="handleClose" data-index="{{index}}">
          <text class="flow">x</text>
        </view>
      </view>
    </block>
    <view wx:if="{{uploadedFilePaths.length < maxCount}}" class="camera" bindtap="handleCameraTap">
      <canvas type="2d" id="uploadCanvas" class="canvas"></canvas>
    </view>
  </view>
</view>

样式

/* components/upload/index.wxss */
.upload{
  margin-top:20rpx;
}
.camera {
  width: 100rpx;
  height: 100rpx;
  border: 1px solid #d9d9d9;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 12rpx;
  background-color: #fff;
  position: relative;
}

.imageList {
  display: flex;
  align-items: center;
}

.imageList_item {
  border-radius: 12rpx;
  position: relative;
  width: 100rpx;
  height: 100rpx;
  margin-right: 20rpx;

}

.close {
  width: 32rpx;
  height: 32rpx;
  background-color: rgba(90, 90, 90, 0.3);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  right: 0;
  top: 0;
  font-size: 24rpx;
  color: #fff;
}

.flow {
  display: flow-root;
  height: 100%;
  line-height: 100%;
}

.canvas {
  width: 100rpx;
  height: 100rpx;
  opacity: 0;
  position: absolute;
  z-index: -1;
  display: none;
}

.upload_img{
  width: 48rpx;
  height: 48rpx;
  border-radius:12rpx;
}
.upload_file{
  width: 100rpx;
  height: 100rpx;
  border-radius: 12rpx;
}

然后在js文件中,调用选择图片的api,由于版本的更迭使用的api也在变化,因此为了能够方便使用把微信小程序中目前常用的选择图片的api封装成一个函数,代码如下:

// chooseImage.js
/**
 * @description 选择图片
 * @param {object} options 选择图片的参数对象
 * @returns {Promise} 返回Promise对象
 */
function chooseImage(options) {
  options = options || {};
  return wx.getSystemInfo().then(res => {
    let {
      system
    } = res;
    let isIOS = /ios/gi.test(system);
    let sizeType = isIOS ? ['compressed'] : ['origin', 'compressed'];
    if (wx.canIUse('chooseImage')) {
      return new Promise((resolve, reject) => {
        wx.chooseImage({
          count: options.count || 9,
          sizeType: options.sizeType || sizeType,
          sourceType: options.sourceType || ['album', 'camera'],
          success: (res) => {
            resolve(res);
          },
          fail: (err) => {
            reject(err);
          }
        });
      });
    } else if (wx.canIUse('chooseMedia')) {
      return new Promise((resolve, reject) => {
        wx.chooseMedia({
          count: options.count || 9,
          mediaType: ['image'],
          sourceType: options.sourceType || ['album', 'camera'],
          success: (res) => {
            res.tempFiles = res.tempFiles.map(item => {
              item.path = item.tempFilePath;
              return item;
            });
            resolve(res);
          },
          fail: (err) => {
            reject(err);
          }
        });
      });
    }
  });
}

module.exports = chooseImage;

引入上面的compressImage.jschooseImage.js的 代码

const chooseImage = require('./chooseImage');
const {
_compressImage
} = require('./compressImage');
Page({
data:{
// 保存上传的文件路径
	uploadFilePaths:[],
	// 上传的最大文件数量
	maxCount:5,
	// 文件大小限制,单位kb,超过限制压缩文件
	limitSize:1024
},
// 上传图片
uploadImg() {
      wx.getSetting().then(res => {});
      if (this.data.uploadedFilePaths.length < this.data.maxCount) {
        const appAuthorizeSetting = wx.getSystemInfoSync(),
        cameraAuthorized = appAuthorizeSetting.cameraAuthorized,
        albumAuthorized = appAuthorizeSetting.albumAuthorized;
        // 相册或相机没有授权,先设置权限
        if ((albumAuthorized !== undefined && !albumAuthorized) || !cameraAuthorized) {
         wx.openAppAuthorizeSetting().then(auth => {}).catch(e => {});
        } else {
          let {
            maxCount
          } = this.data;
          // 授权后选择图片
          chooseImage({
            count: maxCount
          }).then(res => {
            let {
              tempFiles,
            } = res;
            if (tempFiles.length <= maxCount) {
              if ((tempFiles.length + this.data.uploadedFilePaths.length) > maxCount) {
                showToast({
                  title: '最多上传' + maxCount + '张图片'
                });
                return ;
              }
            } else {
              showToast({
                title: '最多上传' + maxCount + '张图片'
              });
              return ;
            }
            this.getCanvas('uploadCanvas').then(canvas => {
              if (canvas) {
                let proArr = tempFiles.map(item => {
                  return compressImage._compressImage(item, canvas, this.data.limitSize);
                });
                Promise.all(proArr).then(res => {
                  this.data.uploadedFilePaths = this.data.uploadedFilePaths.concat(res);
                  this.setData({
                    uploadedFilePaths: this.data.uploadedFilePaths
                  });
                  this.triggerEvent('upload', {
                    value: this.data.uploadedFilePaths
                  });
                });
              }
            });
          }).catch(err => {});
        }
      }
    },
    // 获取canvas,用于压缩图片
    getCanvas(id) {
      return new Promise((resolve, reject) => {
        let query = wx.createSelectorQuery();
        query.in(this);
        query.select(`#${id}`).fields({
          node: true,
          size: true
        }).exec((res) => {
          if (res[0]) {
            resolve(res[0].node);
          } else {
            resolve(null);
          }
        });
      });
    },
})

每种图片处理方式都有其突出的优势,结合多种方式能够最优地解决问题,适用于目标场景,便利用户上传图片的体验。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端御书房

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值