H5-jssdk与原生获取图片、压缩、上传流程的一种实践

上传按钮UI同构

xml 复制代码
<!-- 微信环境(小程序/微信浏览器) -->
<div class="upload-button" v-if="is_WeChat" @click="chooseWeChatImage">上传图片</div>
<!-- 原生H5(pc浏览器/手机浏览器),此处用的vant组件,基本同input上传。 -->
<van-uploader v-else :after-read="chooseRawImage" accept="image/*"
  ref="h5Upload">
  <div class="upload-button" >上传图片</div>
</van-uploader>

微信API & H5原生上传流程

注:调用wx.chooseImage 需要H5正确配置jssdk

javascript 复制代码
<!-- 微信API获取图片 -->
chooseWeChatImage() {
  const self = this
  // 1 选择图片 
  this.$wx.chooseImage({
    count: 1,
    sizeType: ["compressed"],
    sourceType: ["album", "camera"], // 可以指定来源是相册还是相机,默认二者都有
    success: function(res) {
      let id = res.localIds[0]
      self.$loading.show("识别中...")
      // 2 通过图片的localID 获取 图片的base64数据
      self.$wx.getLocalImgData({
        localId: id,
        success: function(res) {
          // 3 base64兼容处理(IOS与安卓兼容)
          let localData = res.localData
          if (localData.indexOf("data:image") != 0) {
            localData = "data:image/jpeg;base64," + localData
          }
          localData = localData.replace(/\r|\n/g, "").replace("data:image/jgp", "data:image/jpeg")
          // 4 base64 转 File
          const file = base64ToFile(localData)
          console.log("base64ToFile", file)
          // 5 上传图片(同时返回 File 和 base64,方便使用 )
          self.uploadImgFile({ file: file, content: localData, compress: false })
        }
      })
    },
    fail(err) {
      console.log("err", err)
    }
  })
}
/**
* 原生获取图片
* @param {Object} result - {content,file,message,status}
**/
chooseRawImage(result){
  this.uploadImgFile({...result, compress: true})
}
/**
 * 上传图片到服务器获取图片链接
 * @param {Object} result - file: 文件;content:base64
 */
async uploadImgFile(result) {
  if (result.compress) {
    // 因上传一般需要传file故仅压缩file,有需要可自行转base64
    const { file } = await compressImg(result.file, 0.7) 
    result.file = file
  }
  const formData = new FormData()
  formData.append("file", res.file)
  try {
    const res = await uploadImgFileApi(formData)
  } catch (error) {
    this.$loading.hide()
    error && console.log("上传图片异常", error)
  }
}

工具方法

1. base64 转 File

javascript 复制代码
/**
 * base64转文件
 * @param {string} base64 - 图片base64字符串
 * @returns {File | null} file
 */
export function base64ToFile(base64) {
  try {
    let bstr = null
    let n = null
    let mime = base64.split(",")[0].match(/:(.*?);/)[1] //mime类型 image/png
    let u8arr = null
    if (base64.indexOf(",") > 0) {
      //  ios兼容
      let arr = base64.split(",")
      bstr = atob(arr[1])
    } else {
      bstr = atob(base64)
    }
    n = bstr.length
    u8arr = new Uint8Array(n)
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n)
    }
    // e_worker_ 表示业务标识,可自行修改
    let file = new File([u8arr], `e_worker_${new Date().getTime()}.${mime.split("/")[1]}`, { type: mime })
    return file
  } catch (err) {
    console.error("异常", err)
    return null
  }
}

2. 图片压缩方法

来源:juejin.cn/post/694043...

ini 复制代码
/**
 * 压缩图片方法
 * @param {file} file 文件
 * @param {Number} quality 图片质量(取值0-1之间默认0.92)
 */
export const compressImg = function(file, quality) {
  var qualitys = 0.52
  console.log(parseInt((file.size / 1024).toFixed(2)))
  if (parseInt((file.size / 1024).toFixed(2)) < 1024) {
    qualitys = 0.85
  }
  if (5 * 1024 < parseInt((file.size / 1024).toFixed(2))) {
    qualitys = 0.92
  }
  if (quality) {
    qualitys = quality
  }
  if (file[0]) {
    return Promise.all(Array.from(file).map(e => compressImg(e, qualitys))) // 如果是 file 数组返回 Promise 数组
  } else {
    return new Promise(resolve => {
      console.log(file)
      if ((file.size / 1024).toFixed(2) < 300) {
        resolve({
          file: file
        })
      } else {
        const reader = new FileReader() // 创建 FileReader
        reader.onload = ({ target: { result: src } }) => {
          const image = new Image() // 创建 img 元素
          image.onload = async () => {
            const canvas = document.createElement("canvas") // 创建 canvas 元素
            const context = canvas.getContext("2d")
            var targetWidth = image.width
            var targetHeight = image.height
            var originWidth = image.width
            var originHeight = image.height
            if (
              1 * 1024 <= parseInt((file.size / 1024).toFixed(2)) &&
              parseInt((file.size / 1024).toFixed(2)) <= 10 * 1024
            ) {
              var maxWidth = 1600
              var maxHeight = 1600
              targetWidth = originWidth
              targetHeight = originHeight
              // 图片尺寸超过的限制
              if (originWidth > maxWidth || originHeight > maxHeight) {
                if (originWidth / originHeight > maxWidth / maxHeight) {
                  // 更宽,按照宽度限定尺寸
                  targetWidth = maxWidth
                  targetHeight = Math.round(maxWidth * (originHeight / originWidth))
                } else {
                  targetHeight = maxHeight
                  targetWidth = Math.round(maxHeight * (originWidth / originHeight))
                }
              }
            }
            if (
              10 * 1024 <= parseInt((file.size / 1024).toFixed(2)) &&
              parseInt((file.size / 1024).toFixed(2)) <= 20 * 1024
            ) {
              maxWidth = 1400
              maxHeight = 1400
              targetWidth = originWidth
              targetHeight = originHeight
              // 图片尺寸超过的限制
              if (originWidth > maxWidth || originHeight > maxHeight) {
                if (originWidth / originHeight > maxWidth / maxHeight) {
                  // 更宽,按照宽度限定尺寸
                  targetWidth = maxWidth
                  targetHeight = Math.round(maxWidth * (originHeight / originWidth))
                } else {
                  targetHeight = maxHeight
                  targetWidth = Math.round(maxHeight * (originWidth / originHeight))
                }
              }
            }
            canvas.width = targetWidth
            canvas.height = targetHeight
            context.clearRect(0, 0, targetWidth, targetHeight)
            context.drawImage(image, 0, 0, targetWidth, targetHeight) // 绘制 canvas
            const canvasURL = canvas.toDataURL("image/jpeg", qualitys)
            const buffer = atob(canvasURL.split(",")[1])
            let length = buffer.length
            const bufferArray = new Uint8Array(new ArrayBuffer(length))
            while (length--) {
              bufferArray[length] = buffer.charCodeAt(length)
            }
            const miniFile = new File([bufferArray], file.name, {
              type: "image/jpeg"
            })
            console.log({
              file: miniFile,
              origin: file,
              beforeSrc: src,
              afterSrc: canvasURL,
              beforeKB: Number((file.size / 1024).toFixed(2)),
              afterKB: Number((miniFile.size / 1024).toFixed(2)),
              qualitys: qualitys
            })
            resolve({
              file: miniFile,
              origin: file,
              beforeSrc: src,
              afterSrc: canvasURL,
              beforeKB: Number((file.size / 1024).toFixed(2)),
              afterKB: Number((miniFile.size / 1024).toFixed(2))
            })
          }
          image.src = src
        }
        reader.readAsDataURL(file)
      }
    })
  }
}

问题与提示

1. wx.chooseImage无法获取原图的问题。

wx.chooseImage 方法虽然提供了获取原图的选项(sizeType),但安卓和IOS仍无法获取到原图。[1]

使用原生上传可以拿到原始大小的文件。

2. 两种方式中,都需要压缩吗?

  • wx.chooseImage 获取的图片本身文件体积较小、质量也尚可,所以可以不用压缩;
  • H5获取的是原始图片,文件体积较大建议压缩图片。

3. 部分情况下,ios手机用原生上传图片为什么依然不是原图?

  • 可能的原因:ios的heic格式图片可能在上传时系统做了转换处理。

参考

  1. 公众号上传图片选择原图被压缩的问题
  2. 前端图片最优化压缩方案 - 掘金
相关推荐
正小安20 分钟前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch2 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光2 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   2 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   2 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web2 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常2 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
莹雨潇潇3 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr3 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
Tiffany_Ho4 小时前
【TypeScript】知识点梳理(三)
前端·typescript