图片加载存在的问题、原因及解决方案
启动页面时加载过多图片

问题分析
如图,页面启动时加载了大约 49 张图,而这些图片请求几乎是并发的,在 Chrome 中,对于同一个域名,最多支持 6 个请求的并发,其他的请求将会推入到队列中等待或停滞不前,直到六个请求之一完成后,队列中新的请求才会发出
解决方案
- 减少首次加载的请求次数,即只加载首屏内的图片,可以通过
getBoundingClientRect方法,获取图片的位置信息,判断其是否在viewport内部
const inViewport = (el) => {
const rect = el.getBoundingClientRect();
return (
rect.top > 0 &&
rect.left > 0 &&
rect.bottom < window.innerHeight &&
rect.right < window.innerWidth
);
};
- 如果元素没有插入到 DOM 树中并渲染,怎么能判断是否在首屏中?
<!-- defer 为true 优先请求加载 -->
<img v-img="{hash: 'xxx', defer: true}" />
const promises = []; // 用来存储优先加载的图片
Vue.directive("img", {
// bind: 只调用一次,指令第一次绑定到元素是调用。在这里可以进行一次性的初始化设置
bind(el, binding, vnode) {
const { defer } = binding.value;
if (!defer) {
promises.push(update(el, binding, vnode));
}
},
// inserted: 被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)
inserted(elm, binding, vnode) {
const { defer } = binding.value;
if (!defer) return;
if (inViewport(el)) {
promises.push(update(el, binding, vnode));
} else {
Vue.nextTick(() => {
Promise.all(promises)
.then(() => {
promises.length = 0;
update(el, binding, vnode);
})
.catch(() => {});
});
}
},
});
-
对于同域名只支持 6 个并发请求,可以进行域名切分,来提升并发的请求数量
-
使用 HTTP/2 协议
部分图片体积过大

问题分析
如图,该图片主要耗时在Content Download阶段,图片体积过大,直接导致下载图片时间过长
解决方案
-
减小图片大小
-
FileSize = Total Number Pixels * Bytes of Encode single Pixels -
一张
100px*100px像素的图片,就有100*100=10000个像素点,而每个像素点通过RGBA颜色值进行存储,R/G/B/A每个色道都有 0~255 个取值,即2^8=256。正好 8 位 1byte,即每个像素点 4bytes。因此该图片体积10000*4bytes=40000bytes=39KB -
单位像素优化
-
有损的删除一些像素数据:减少每个色道的颜色值来减少单位像素的字节数
-
无损的图片像素压缩:通过算法将颜色值相近的像素压缩
-
jpeg/png/gif/webp 合理使用
-
图片像素总数优化
-
使用 lib-flexible 来对不同的移动端进行适配。
lib-flexible在 HTML 元素添加了两个属性data-dpr和style
const resize = (size) => {
let viewWidth
const dpr = window.devicePixelRatio
const html = document.documentElement
const dataDpr = html.getAttribute('data-dpr')
const ratio = dataDpr ? (dpr / dataDpr) : dpr
\
try {
viewWidth = +(html.getAttribute('style').match(/(\d+)/) || [])[1]
} catch(e) {
const w = html.offsetWidth
if (w / dpr > 540) {
viewWidth = 540 \* dpr / 10
} else {
viewWidth = w / 10
}
}
viewWidth = viewWidth \* ratio
if (Number(viewWidth) >= 0 && typeof viewWidth === 'number') {
return (size \* viewWidth) / 75 // 75 is the 1/10 iphone6 deivce width pixel
} else {
return size
}
}