问题提出
在开发h5页面的时候会经常涉及到图片的使用,对于图片的使用优化前端解决方案颇多,比如图片的懒加载等。但是有另外一种常见情况,网上却鲜有解决方案,那就是交互类图片异步显示问题。
举个例子,比如页面中有一个规则弹窗,弹窗背景使用的是图片的形式呈现,在用户网络不稳定情况下,就会发生弹窗已经出现但是弹窗背景并未加载完成,造成弹窗添加微动效无法正常展示,严重影响用户体验,如图示:
原因简述
造成图片出现延迟显示的原因很简单,因为页面中的图片不论是通过<img>
标签还是通过background
属性加载,只要dom元素没有挂载或是元素被设置display: none;
,浏览器都不会去请求加载该图片资源,只有当元素被挂载到dom树上时,浏览器才会请求图片资源,这时候弹窗已经展示出来,弹窗的动效已经开始执行。
解决方案
对于这种情况,肯定很多小伙伴会说直接把图片打包时转化成base64文件保存在项目代码中不就可以了吗?当然,这不失为一种解决方案,但是这样解决无疑会增大项目文件体积,如果是单页面应用会严重影响页面首屏加载。
对于项目中的图片资源预加载,我们其实可以成分利用浏览器的缓存,即当用户完成页面资源请求时,我们对需要异步展示的图片资源先进行预请求,用户真正展示图片资源时,直接使用浏览器中缓存资源,这样就可以实现图片的立即展示了。我们可以对异步图片资源进行提取,在页面资源加载完成时,手动加载异步图片资源:
const loadArr = [
'//pic0.iqiyipic.com/lequ/20220408/7c16eaa957964cacb746b890d93d2860.gif',
'//pic3.iqiyipic.com/lequ/20220406/db90143202474bddb901da283aa1cd7d.png',
'//pic2.iqiyipic.com/lequ/20220407/3d48af83678c4416afc38e2bd52a4672.png',
]
window.onload=()=>{
loadArr.forEach((src) => {
try {
const image = new Image()
image.src = src
} catch (err) {
console.log('err: ', err)
}
})
}
上面的解决方案,完全依赖于手动提取,如果项目较大很容易出现遗漏或者忘记替换的情况发生,有没有自动化解决方案呢?答案是肯定的,也是我们项目中目前在用的解决方案,那就是通过自定义语法标注异步图片资源,再通过自定义loader进行异步图片资源提取,最后利用plugin在webpack打包完成前在打包结果js文件中植入自执行函数,在页面onload事件中加载图片资源,具体代码实现如下:
在自定义loader中通过正则匹配使用$[]
标注的图片路径,并将其收集到node全局对象的$$preImgLoadArr
属性中。
// preImgLoader.js
module.exports = function (content) {
content = content.replace(/\$\[([\w|\:|\\|\.|\_|\-|\/|\?|\=|\,|\']+)\]/gi, (str) => {
str = str.slice(2, -1)
if (!global.$$preImgLoadArr) global.$$preImgLoadArr = []
!global.$$preImgLoadArr.includes(str) && global.$$preImgLoadArr.push(str)
return str
})
return content;
}
通过webpack编译器compiler获取打包完成前生命周期,插入自执行函数。
// preImgPlugin.js
function preImgPlugin(options) {
this.options = options || {}
}
preImgPlugin.prototype.apply = function (compiler) {
compiler.hooks.emit.tapAsync('preImgPlugin', function (compilation, cb) {
if (global.$$preImgLoadArr) {
for (let key in compilation.assets) {
if (key.includes('.min.js') && compilation.assets[key] && compilation.assets[key]._value) {
compilation.assets[key]._value += `;(function () { var arr = [${global.$$preImgLoadArr}]; window.onload = function () {console.log('Picture preload'); for (let src of arr) { try { var image = new Image(); image.src = src; } catch (err) {console.log(err);}}}})();`
}
}
global.$$preImgLoadArr = null
}
cb()
})
}
module.exports = preImgPlugin
webpack中配置如下:
module.exports = {
module: {
rules: [
{
test: /\.vue$/,
use: [
'vue-loader',
// 图片预加载loader配置
path.resolve(__dirname,'./config/loaders/preImgLoader')
],
},
],
},
plugins:[
// 图片预加载plugin配置
new PreImgPlugin()
],
}
页面中使用:
.rule-box {
.size(645px,769px);
.po-relative;
background: url($['//pic2.iqiyipic.com/lequ/20220428/d40031608ce8479583dc065439ad51dd.png'])
no-repeat 0 0 / contain;
}
通过上述配置,我们在打包的js文件中可以看到自执行函数的插入,在页面加载完成时页面会主动请求预加载图片资源并缓存,从而解决了异步图片延时显示的问题。