关于前端:前端图片资源优化实践

6次阅读

共计 3395 个字符,预计需要花费 9 分钟才能阅读完成。

写在后面

前段时间遇到了一个比拟有意思的 GIF 图相干的问题,上面我先简略说一下遇到的问题,而后再说一下是怎么做资源解决的。

问题

页面中有很多个雷同的 GIF,能够手动触发播放其中一个。(这里播放的计划就不说了,间接说一下播放过程中遇到的问题)

简略来说,预期是这样的:

理论是这样的

问题总结:触发其中一个播放的时候,其余雷同的 GIF 动画也会被一起被影响。

剖析

先看一下这些 GIF 图的资源构造:

能够看到,这些 GIF 用的都是雷同的资源门路。所以在第一个资源回来的时候,会在浏览器造成一个图片缓存,后续其余 GIF 资源用的都是这个缓存。所以触发其中一个 GIF 图播放的时候,相当于播放了存在缓存中的 GIF 图。这个时候,其余的援用了这个缓存资源的中央也就造成了一起播放的成果。

剖析总结:这些 gif 图应用了雷同的浏览器缓存。

高级计划

根底实现

计划外围:分化图片源
这个计划是将源文件复制成 N 多份,而后每个前端的援用映射一个 CDN 源文件,从图片源上将这些雷同的图片给独立开。
举个栗子,原图的名字是“demo.gif”,而后复制“demo.gif”,生成“demo001.gif”、“demo002.gif”、“demo003.gif”…。而后前端应用图片的时候,第一张图用“demo001.gif”、第二张用“demo002.gif”、第三张用“demo003.gif”…。依此类推 …

资源构造如下:

预加载

const baseUrl = 'http://www.abc.com/'
// 这里包层数组是为了拓展其余图片 
const imgArr = [{len: 15, nameFrag: 'demo_00'}]
let imgData = [];
// imgData 最初变成一个 url 列表
// 如:['http://www.abc.com/demo_001.gif','http://www.abc.com/demo_001.gif' ...]
imgArr.forEach((imgConfig) => {
    // new Array 兼容性有问题 能够思考换成 for 循环
    const arr = new Array(imgConfig.len).fill('').map((item,index) => `${baseUrl}${imgConfig.nameFrag}${index+1}.gif`);
    imgData = imgData.concat(arr)
})

// 拿到一个 promise 列表,最初用 promise.all 解决所有预加载
const promiseAll = imgData.map(function (item, index) {return new Promise(function (resolve, reject) {const img = new Image();
      img.onload = function () {
        img.onload = null;
        resolve(img);
      };
      img.error = function () {reject('图片加载失败');
      };
      img.src = item;
    });
 });
 
Promise.all(promiseAll).then(function () {do something...},
    function (err) {do something...}
);

毛病:

1、资源更新老本高

举个例子,你的网页上原来有 15 张“点赞.gif”,UI 同学忽然哪天来灵感了,她要把“点赞.gif”替换成“疯狂点赞.gif”。

那这个时候麻烦就来了,你要生成 15 张新的“疯狂点赞.gif”,别离是“疯狂点赞 001.gif”、“疯狂点赞 002.gif”、“疯狂点赞 003.gif”…。而后再把原来的 15 张“点赞.gif”替换调。而后还要前端的援用的图片名一个个改掉。

15 张可能还能承受,然而如果再来 15 张“点踩.gif”,或者其余更多雷同 gif 呢?那麻烦就大了,这个时候就是经典的吐槽 UI 工夫 …

2、节约存储(cdn)资源

这个很好了解,前端每个援用的图片源都是要存起来的嘛,那理论只须要存 1 张的图片,这个计划须要存 15 张,相当于资源耗费是原来的 N 倍。

还是毛病 1 雷同的问题,简略场景下没有问题,然而场景越简单,存储资源的节约越大。

降级计划

根底实现

计划外围:分化浏览器缓存
后面说的计划一是生成多个图片源,让前端援用和图片源一对一映射关系。那这个计划的外围就是生成多个浏览器缓存,让前端援用和浏览器缓存造成一对一映射关系。
具体就是,在每个图片申请上加一个固定的参数编号,相当于给浏览器缓存打上一个编号。
比方:
原图片门路是:“http://www.abc.com/demo.gif”
新图片门路就是:
“http://www.abc.com/demo.gif?001”
“http://www.abc.com/demo.gif?002”
“http://www.abc.com/demo.gif?003”
……

图片资源构造如下:

这个计划额定须要做的一件事件就是,在实现的时候保护一个参数映射关系,保护的模式能够有很多,这里提供两种简略倡议:

// 第一种
// 间接将参数编号列出来
// 每用到一个用到一个,则 index++
cosnt base_url = 'http://www.baidu.com/'
const imgMap = {
    "demo.gif": {
        index: 0,
        list:['001','002','003'......]
    }
}
const url_001 = `${base_url}demo.gif?${imgMap['demo.gif'].list[imgMap['demo.gif'].index]}`
imgMap['demo.gif'].index++
const url_001 = `${base_url}demo.gif?${imgMap['demo.gif'].list[imgMap['demo.gif'].index]}`
imgMap['demo.gif'].index++
.....

// 第二种 
// 不必存 list,依据 index 实时计算就行
// 须要保障 图片援用的时候 和 预加载的时候 的算法统一就行
// 这里个代码就不列举了

预加载

预加载和计划一的没什么大的区别,只是在生成 url 的时候,略微改一下就行

const arr = new Array(imgConfig.len).fill('').map((item,index) => `${baseUrl}${imgConfig.nameFrag}.gif?${index+1}`);

全副代码:

const baseUrl = 'http://www.abc.com/'
// 这里包层数组是为了拓展其余图片 
const imgArr = [{len: 15, nameFrag: 'demo'}]
let imgData = [];
// imgData 最初变成一个 url 列表
// 如:['http://www.abc.com/demo_001.gif','http://www.abc.com/demo_001.gif' ...]
imgArr.forEach((imgConfig) => {
    // new Array 兼容性有问题 能够思考换成 for 循环
    const arr = new Array(imgConfig.len).fill('').map((item,index) => `${baseUrl}${imgConfig.nameFrag}.gif?${index+1}`);
    imgData = imgData.concat(arr)
})

// 拿到一个 promise 列表,最初用 promise.all 解决所有预加载
const promiseAll = imgData.map(function (item, index) {return new Promise(function (resolve, reject) {const img = new Image();
      img.onload = function () {
        img.onload = null;
        resolve(img);
      };
      img.error = function () {reject('图片加载失败');
      };
      img.src = item;
    });
 });
 
Promise.all(promiseAll).then(function () {do something...},
    function (err) {do something...}
);
正文完
 0