【開發(fā)】微信小程序圖片上傳壓縮代碼及注意事項(xiàng)
當(dāng)前位置:點(diǎn)晴教程→知識(shí)管理交流
→『 技術(shù)文檔交流 』
上傳圖片是小程序常見的功能,例如點(diǎn)評(píng)類小程序邀請(qǐng)用戶分享照片、電商類小程序要求商家上傳商品照片。 伴隨著照片像素越來越高,圖片體積越來越大,小程序開發(fā)者需要壓縮圖片,否則將導(dǎo)致用戶上傳圖片失敗或加載時(shí)間過長等影響體驗(yàn)的情況。 小程序提供 wx.chooseMedia、wx.canvasToTempFilePath、wx.compressImage 3 個(gè)圖片類接口,便于開發(fā)者在不同應(yīng)用場景下處理圖片。除此以外,這 3 個(gè)接口的巧妙結(jié)合能夠滿足更多元化的圖片壓縮需求。下面就來看看怎樣使用吧! wx.chooseMedia wx.chooseMedia 支持在使用小程序過程中拍攝或從手機(jī)相冊(cè)選擇圖片或視頻,其 sizeType 屬性支持是否上傳縮略圖。該接口應(yīng)用簡便,接入即可實(shí)現(xiàn)壓縮圖片效果,省時(shí)省力。 wx.chooseMedia({ count: 9, mediaType: ['image'], // 只允許選擇圖片 sourceType: ['album', 'camera'], // 可以拍攝或從相冊(cè)中選擇 sizeType:['compressed'], // 選擇壓縮圖 camera: 'back', // 后置攝像頭 success(res) { console.log(res) } }); 然而,該接口在壓縮圖片方面也有一定的限制: 無法指定壓縮質(zhì)量 部分安卓機(jī)型存在壓縮失效的情況 iOS 和安卓的壓縮機(jī)制不同,需要進(jìn)行合理兼容 wx.canvasToTempFilePath 開發(fā)者可以通過控制 Canvas.createImage 繪制圖片到 Canvas,然后利用 wx.canvasToTempFilePath 接口轉(zhuǎn)換成圖片。 這種方法能夠高效控制圖片寬高尺寸以及壓縮質(zhì)量,非常適用于有圖片要求的場景。 wx.canvasToTempFilePath({ width: 50, // 畫布區(qū)域的寬度 height: 50, // 畫布區(qū)域的高度 destWidth: 100, // 輸出圖片的寬度 destHeight: 100, // 輸出圖片的高度 canvasId: 'myCanvas', quality: 1, // 圖片質(zhì)量0-1 success(res) { console.log(res.tempFilePath) } }); 但是這種方式也會(huì)存在一定的限制: iOS 和安卓的壓縮機(jī)制不同,需要進(jìn)行合理兼容 通過 Canvas 轉(zhuǎn)換的圖片存在略微色差 wx.compressImage 開發(fā)者可以調(diào)用wx.compressImage 接口直接壓縮圖片,而且支持選擇壓縮質(zhì)量,不限制圖片寬高尺寸,非常適用于處理特殊大小的圖片。 wx.compressImage({ src: '', // 圖片路徑 quality: 80 // 壓縮質(zhì)量 0-100 }); 同時(shí)這種方式也需要考慮不同系統(tǒng)的壓縮差異: 在壓縮到極限值時(shí),iOS 壓縮圖畫質(zhì)不會(huì)隨著壓縮質(zhì)量變小而變化 在壓縮質(zhì)量小于 1 時(shí),安卓系統(tǒng)輸出的畫質(zhì)將不再變小 多方式結(jié)合處理 回顧常見的小程序業(yè)務(wù)場景,圖片處理主要聚焦于用戶上傳圖片、列表展示這 2 個(gè)環(huán)節(jié),可以結(jié)合以上 3 個(gè)接口實(shí)現(xiàn)最佳圖片處理方式,既能夠利用接口自帶的壓縮功能,省時(shí)省力;又能夠解決圖片太大造成的壓縮難題。 判斷系統(tǒng)類型 判斷當(dāng)前系統(tǒng)是 iOS 系統(tǒng)還是安卓系統(tǒng) function isIOS(){ return wx.getSystemInfo().then(res => { return /IOS/ig.test(res.system); }); 根據(jù)系統(tǒng)選擇上傳方式 iOS 系統(tǒng):設(shè)置 sizeType 為 [‘compressed’],利用 iOS 壓縮體系自動(dòng)壓縮 安卓系統(tǒng):設(shè)置 sizeType 為 [‘original’, ‘compressed’],讓用戶自主選擇上傳原圖或壓縮圖。 這種方式一方面利用接口自帶的壓縮能力; 另一方面如果圖片寬高大于安卓能清晰壓縮的值(例如40000),用戶會(huì)預(yù)覽到比較模糊的照片而選擇上傳原圖 驗(yàn)證大小,手動(dòng)壓縮 當(dāng)用戶選擇圖片后,wx.chooseMedia 返回的 tempFiles 顯示對(duì)應(yīng)圖片的大小。如果該圖片大小大于限制值,則進(jìn)行手動(dòng)壓縮。 根據(jù)寬高選擇壓縮方式 通過 wx.getImageInfo 獲取圖片的寬高: 如果寬度或高度大于 4096,調(diào)用 wx.compressImage 強(qiáng)制壓縮 如果寬度和高度都小于 4096,繪制 Canvas 實(shí)現(xiàn)壓縮,設(shè)置壓縮基礎(chǔ)寬高為 1280 代碼如下: // compressImage.js /** * @param {object} img 包含path:圖片的path,size:圖片的大小 * @param {object} canvas canvas對(duì)象 * @param {number} fileLimit 文件大小限制 * @returns {Promise} 返回Promise對(duì)象 */ function _compressImage(img, canvas, fileLimit) { return wx.getSystemInfo().then(res => { let { // 設(shè)備像素比 pixelRatio, // 設(shè)備品牌 system } = res; // 是否是IOS系統(tǒng) let isIOS = /(ios)/ig.test(system); // 文件限制 fileLimit = fileLimit || 2 * 1024 * 1024; // 基礎(chǔ)大小 let baseSize = 1280; // 大于文件限制,手動(dòng)壓縮 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 根據(jù)圖片的大小選擇壓縮的方式 * @param {string} src 圖片的path * @param {number} size 圖片的大小 * @param {object} canvas canvas對(duì)象 * @param {number} baseSize 基礎(chǔ)尺寸 * @param {boolean} isIOS 是否是IOS系統(tǒng) * @returns {Promise} 返回Promise對(duì)象 */ 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使用強(qiáng)制壓縮 compressImage(src, size, isIOS).then(response => { resolve(response); }); } }).catch(err => { // 使用強(qiáng)制壓縮 compressImage(src, size, isIOS).then(response => { resolve(response); }); }); }); } /** * @description 使用wx.compressImage壓縮圖片 * @param {string} src 圖片的path * @param {number} size 圖片的大小 * @param {boolean} isIOS 是否是IOS系統(tǒng) * @returns {Promise} 返回Promise對(duì)象 */ 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對(duì)象 * @param {number} baseSize 基礎(chǔ)尺寸 * @param {boolean} isIOS 是否是IOS系統(tǒng) * @param {number} pixelRatio 設(shè)備像素比 * @returns {Promise} 返回Promise對(duì)象 */ 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; } // 設(shè)置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); // 寬比高大,寬按基準(zhǔn)比例縮放,高設(shè)置為基準(zhǔn)值,高比寬大,高按基準(zhǔn)比例縮放,寬設(shè)置為基準(zhǔn)值。 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 () { // 加載失敗使用強(qiáng)制壓縮 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); // 導(dǎo)出圖片 wx.canvasToTempFilePath({ canvas, width: canvasWidth, height: canvasHeight, destHeight: canvasHeight, destWidth: canvasWidth, fileType:'jpg', quality, success: (res) => { resolve(res.tempFilePath); }, fail: (err) => { // 壓縮失敗使用強(qiáng)制壓縮 compressImage(src, size, isIOS).then(response => { resolve(response); }); } }); } }); } /** * @description 循環(huán)壓縮圖片 * @param {object} img 包含path:圖片的path,size:圖片的大小 * @param {object} canvas canvas對(duì)象 * @param {number} fileLimit 文件大小限制 * @returns {Promise} 返回Promise對(duì)象 */ 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文件中,調(diào)用選擇圖片的api,由于版本的更迭使用的api也在變化,因此為了能夠方便使用把微信小程序中目前常用的選擇圖片的api封裝成一個(gè)函數(shù),代碼如下: // chooseImage.js /** * @description 選擇圖片 * @param {object} options 選擇圖片的參數(shù)對(duì)象 * @returns {Promise} 返回Promise對(duì)象 */ 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.js和chooseImage.js的 代碼 const chooseImage = require('./chooseImage'); const { _compressImage } = require('./compressImage'); Page({ data:{ // 保存上傳的文件路徑 uploadFilePaths:[], // 上傳的最大文件數(shù)量 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; // 相冊(cè)或相機(jī)沒有授權(quán),先設(shè)置權(quán)限 if ((albumAuthorized !== undefined && !albumAuthorized) || !cameraAuthorized) { wx.openAppAuthorizeSetting().then(auth => {}).catch(e => {}); } else { let { maxCount } = this.data; // 授權(quán)后選擇圖片 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); } }); }); }, }) 每種圖片處理方式都有其突出的優(yōu)勢,結(jié)合多種方式能夠最優(yōu)地解決問題,適用于目標(biāo)場景,便利用戶上傳圖片的體驗(yàn)。 ———————————————— 版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接和本聲明。 原文鏈接:https://blog.csdn.net/qq_40850839/article/details/131287452 該文章在 2024/3/24 16:56:32 編輯過 |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |