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. 前端图片最优化压缩方案 - 掘金
相关推荐
小林爱3 分钟前
【Compose multiplatform教程06】用IDEA编译Compose Multiplatform常见问题
android·java·前端·kotlin·intellij-idea·compose·多平台
蜗牛快跑2133 小时前
前端正在被“锈”化
前端·代码规范
Jet_closer_burning5 小时前
微信小程序中遇到过的问题
前端·微信小程序·小程序
掘金酱6 小时前
稀土掘金社区2024年度影响力榜单正式公布
android·前端·后端
Keven__Java6 小时前
Java开发-后端请求成功,前端显示失败
java·开发语言·前端
轻口味6 小时前
【每日学点鸿蒙知识】渐变效果、Web组件注册对象报错、深拷贝list、loadContent数据共享、半屏弹窗
前端·list·harmonyos
老K(郭云开)6 小时前
最新版Chrome浏览器加载ActiveX控件技术——alWebPlugin中间件V2.0.28-迎春版发布
前端·chrome·中间件
轻口味6 小时前
【每日学点鸿蒙知识】子窗口方向、RichEdit不居中、本地资源缓存给web、Json转对象丢失方法、监听状态变量数组中内容改变
前端·缓存·harmonyos
我是苏苏7 小时前
Web开发:ORM框架之使用Freesql的分表分页写法
前端·数据库·sql
m0_674031437 小时前
React - useContext和深层传递参数
前端·javascript·react.js