前端如何处理压缩几百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,类似于洗数据一样。欢迎有别的方式处理大图预览的方案交流。