前端如何处理几百MB大图进行预览

前端如何处理压缩几百MB大图进行预览

需求是把好几百MB的大图在前端进行预览,并且支持TIFF格式


前言

一般前端预览插件支持的图片大小都比较小,超过几MB后都可能报错,常见的做法是后端进行处理,再返回给前端。也许有好多前端小伙伴说,市面上有压缩插件,但是经过我的实践,图片太大后插件也会报错,小图支持,还有就是火狐浏览器支持更低,在chrome预览可以,火狐就不行,这就满足不了全兼容大图预览的功能,但是前端经过我的摸索,一样可以将好几百MB的大图进行压缩处理并预览,上方法。


一、需要的技术支持

1、安装tiff.js(处理TIFF、TIF图片),如果小伙伴的图片不需要查看tiff,可以跳过;
2、预览插件,photoswipe-lightbox.esm.min.js,其实预览插件可以根据自己的需要选择,最重要的其实是压缩图片后的数据,用什么预览插件展示数据,都可以。
3、图片压缩的原理知识,其实就是通过canvas技术,转化file和base64来处理大图,压缩到合适的数据大小,怎么转,才能达到这样的效果呢,以下是我的实践方法,给大家做个参考。

二、使用步骤

1.HTML准备

 <div class="overview-box" v-show="isShow" id="boxV">
        <canvas ref="canvas" class="canvas-el" style="opacity: 0"></canvas>
        <p class="tips">{{ imgTips }}</p>
        <img :src="imgUrl" style="opacity: 0" id="imgEL" />
 </div>

2.省略插件安装,获取以及处理数据

总的图片预览处理函数,关注***说明,后面会拆解给大家说明:

 viewType(item, str) {
 //业务需要,和预览无关,start
      if (!item.bucket) {
        this.$Tips_common('参数异常,请稍后再试!', 'warn')
        return false
      }
      this.isShow = true
      this.imgTips = '正在获取图片信息,请稍后~'
      this.$Spinner_common(false)
      this.currentItem = item
      if (this.isOprate) {
        this.$Tips_common('正在处理,请等待预览或下载完成后再操作', 'warn')
        return false
      }
      if (str) {
        this.$Tips_common(str, 'warn')
      }
      this.isOprate = true
       //业务需要,和预览无关,end
      let fn = (result) => {
        //***公共处理base64回调函数,后面使用
        this.imgShow = false
        this.imgTips = '图片正在计算渲染,请稍后~'
        if (result) {
          this.imgUrl = result
          let boxV = document.getElementById('boxV')
          let img = boxV.querySelector('img')
          this.timerOne = setTimeout(() => {
            this.initPicView(result, img.getBoundingClientRect())//这里使用的是PhotoSwipeLightbox预览图片
            this.$Spinner_common(false)
          }, 200)
        } else {
          this.$Spinner_common(false)
          this.isOprate = false
        }
      }
      this.getImgData(item, (res, viewData, type) => {//***获取图片数据
        if (!res.data) {
          this.$Spinner_common(false)
          this.$Tips_common('图片数据获取异常!', 'warn')
          this.isOprate = false
          return false
        }
        const objectKeyArr = item.key.split('.')
        let typeName = objectKeyArr[objectKeyArr.length - 1]
        const tif_fn = () => {//***tiff图片处理方式
          Tiff.initialize({ TOTAL_MEMORY: 973041000 })
          this.imgTips = '正在解析数据~'
          let isOK = false
          this.timerTwo = setTimeout(() => {
            if (!isOK) {
              this.imgTips = '数据解析失败,请确认图片数据是否可用'
            } else {
              this.imgTips = '正在解析数据~'
            }
          }, 8000)

          let tiff = null
          tiff = new Tiff({ buffer: res.data })
          if (!tiff) {
            console.log(
              '解析tiff数据失败,数据可能不是正常数据流,请确认',
              res.data,
            )
            return false
          }
          isOK = true
          for (let i = 0, len = tiff.countDirectory(); i < len; ++i) {
            tiff.setDirectory(i)
            var imgs = tiff.toDataURL() // 转化成base64
            var canvas = tiff.toCanvas()
            console.log('第一次canvas创建', canvas)
            this.base64ToFile(imgs, fn)
          }
        }
        const chooseTypeFn = (str) => {//***判断图片格式,选择哪种处理方式
          if (str.indexOf('tiff') >= 0 || str.indexOf('tif') >= 0) {
            console.log('*************关键信息:TIF处理方式')
            tif_fn()
          } else {
            console.log('*************关键信息:非TIF处理方式')
            this.arrayBufferToBase64(res.data, fn, type)//***自定义处理压缩图片,后面会详细说明
            //除了火狐,其他浏览器都没问题,下面的方式火狐有问题,生产放弃使用
            //let blob = new Blob([res.data])
            //this.compressorImage(blob, fn, 0.3)
          }
        }
        if (typeName) {
          //***优先判断图片后缀格式
          chooseTypeFn(typeName)
        } else {
          this.$Tips_common('图片格式获取异常,可能无法正常渲染!', 'warn')
          chooseTypeFn(type) //判断content-type
        }
      })
    },

TIFF格式处理,代码如下(示例):

const tif_fn = () => {
          Tiff.initialize({ TOTAL_MEMORY: 973041000 })//***初始化tiff.js,这里设置了需要支持可处理的图片大小,默认的比较小,很重要这步,详细可查官网作者的解释
          this.imgTips = '正在解析数据~'
          let isOK = false
          this.timerTwo = setTimeout(() => {
            if (!isOK) {
              this.imgTips = '数据解析失败,请确认图片数据是否可用'
            } else {
              this.imgTips = '正在解析数据~'
            }
          }, 8000)

          let tiff = null
          tiff = new Tiff({ buffer: res.data })//***处理数据
          if (!tiff) {
            console.log(
              '解析tiff数据失败,数据可能不是正常数据流,请确认',
              res.data,
            )
            return false
          }
          isOK = true
          for (let i = 0, len = tiff.countDirectory(); i < len; ++i) {
            tiff.setDirectory(i)
            var imgs = tiff.toDataURL() // 转化成base64
            var canvas = tiff.toCanvas()
            console.log('第一次canvas创建', canvas)
            this.base64ToFile(imgs, fn)//***自定义base64转文件格式
          }
        }

base64ToFile函数说明,base64转file(为了兼容火狐浏览器,必须把base64转成文件格式,再转成base64,这里的原理不知道为什么火狐要这样处理,总之解决了问题,有了解的朋友,欢迎留言告知):

 base64ToFile(imgSrc, fn) {
      //***转file,为了压缩大图base64,图片不能直接渲染大图base64,数据体积大会报错
      let file = this.dataURLtoFile(imgSrc, '预览图片')//很关键的一环,为了兼容火狐的处理
      if (file.size > 102400) {//图片太大,再进行二次处理
        console.log(
          'file.size>102400,开始进行展示图片压缩处理——————————————————',
        )
        this.cutImageBase64(file, 1900, 0.3, fn)
      } else {
      //可直接使用
        fn(imgSrc)
      }
    },
    //***为了兼容火狐的处理,费了大量的时间试验出来的方法
     dataURLtoFile(dataurl, filename, fn) {
      //为了兼容火狐浏览器,必须把base64转成文件格式,再转成base64,这里的原理不知道为什么火狐要这样处理,总之解决了问题,有了解的朋友,欢迎留言告知
      var arr = dataurl.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n)
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n)
      }
      return new File([u8arr], filename, { type: mime })
    },

cutImageBase64函数说明,压缩图片获得更小图片的base64:

 cutImageBase64(file, wid, quality, fn) {
      //***将转好的file再次转base64(压缩)
      var URL = window.URL || window.webkitURL
      var blob = URL.createObjectURL(file)
      var base64
      var img = new Image()
      img.src = ''
      img.src = blob
      img.onload = function () {
        var that = this
        //生成比例
        var w = that.width,
          h = that.height
        console.log(that.width, that.height, '图片原始宽高')
        //生成canvas
        let canvas = document.querySelector('.canvas-el')
        // canvas.width = w
        // canvas.height = h

        if (w >= h) {
          canvas.width = w
          // 等比例缩小
          canvas.height = w * (h / w)
        } else {
          canvas.height = w
          // 等比例缩小
          canvas.width = w * (w / h)
        }

        var ctx = canvas.getContext('2d')
        ctx.drawImage(that, 0, 0, w, h)
        base64 = canvas.toDataURL('image/jpeg', quality || 0.9)
        fn(base64)//***将转好的base64传入回调
        console.log(
          '展示图片压缩后:',
          parseInt(base64.length / 2014, 0) + 'kb',
        )
      }
      img.onerror = function () {
        this.$Tips_common('图片加载失败,请刷新重试!', 'warn')
      }
    },

arrayBufferToBase64函数说明(后端返回的数据是arrayBuffer,若是返回数据是base64,可省略),arrayBuffer转base64:

arrayBufferToBase64(buffer, fn, type) {
      //转base64
      let canvas = document.querySelector('.canvas-el')
      const ctx = canvas.getContext('2d')
      var image = document.getElementById('imgEL')
      const _buffer = Buffer.from(buffer, 'base64', ctx.drawImage(image, 0, 0))
      const imgSrc = 'data:' + type + ';base64,' + _buffer.toString('base64')
      this.imgUrl = imgSrc
      this.base64ToFile(imgSrc, fn)//***上面已详细说明
    },

最后一步的处理,将file数据通过插件预览到界面:

// 初始化图片view
    initPicView(href, obj) {
      const gallery = document.getElementById('my-gallery')
      const lightbox = new PhotoSwipeLightbox({//***插件,可根据实际情况换插件使用
        gallery: '#my-gallery',
        children: 'a',
        pswpModule: () => import('@/assets/js/photoswipe.esm.min.js'),
        clickToCloseNonZoomable: false,
        doubleTapAction: false,
      })
      const fn = (a) => {
        a.href = href
        a.setAttribute('data-pswp-width', obj.width / 0.4)
        a.setAttribute('data-pswp-height', obj.height / 0.4)
        a.click()
        this.timerThere = setTimeout(() => {
          console.log('预览样式调整~')
          let box = document.querySelector('.pswp')
          let btn = box.querySelectorAll('.pswp__button')
          let bar = document.querySelector('.pswp__top-bar')
          if (bar) {
            bar.style.height = '60px'
          }
          if (btn) {
            btn.forEach((item) => {
              item.style.background = 'none'
              item.style.width = '50px'
              item.style.height = '60px'
            })
          }
        }, 400)
        const _this = this
        const closeFn = () => {
          _this.isOprate = false
          _this.isShow = false
          clearTimeout(this.timerOne)
          clearTimeout(this.timerTwo)
          clearTimeout(this.timerThere)
        }
        _this.closeBgFn()
        const swalEl = document.querySelector('.swal2-container') //用户可能点击关闭弹窗时,图片刚好渲染出来
        if (swalEl) {
          swalEl.style.display = 'none'
        }
      }
      if (!gallery) {
        const oDiv = document.createElement('div')
        const a = document.createElement('a')
        oDiv.id = 'my-gallery'
        a.target = '_blank'
        oDiv.append(a)
        document.body.append(oDiv)
        lightbox.init()
        fn(a)
      } else {
        lightbox.init()
        const a_el = gallery.querySelector('a')
        fn(a_el)
      }
    },

在这里插入图片描述
100MB的图片,点击预览后
在这里插入图片描述
100MB的预览计算时长大概14秒左右
在这里插入图片描述

最终预览效果


总结

以上是处理大图预览的方式及思路,总结下来就是,1、先判断图片类型,选择是tiff处理还是普通图片处理;2、tiff通过tiff.js处理,3、普通图片,分大图和小图的处理,小图直接转成base64,转成file展示,大图需要进行压缩(这里试过别的插件,也是有兼容问题,后面就自己处理了),然后再走小图的流程,4、其中将base64需要进行一次转换,兼容火狐浏览器,即转为file后再次转为base64,类似于洗数据一样。欢迎有别的方式处理大图预览的方案交流。

  • 18
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值