vue实际项目中关于图片的大量加载的优化及思路
前言: 项目中的问题首先是没有缓存时进入页面时,点击切换等功能造成的, 这个时候谷歌浏览器中的network中会无缓存请求图片所在的地址,短则1s 长则2s,对用户感觉不是很友好。于是乎第一种解决方法出来了:图片预加载,尽管它仍然有局限性;图片预加载let {radarImgArray,radarSource,cloudImgArray,cloudSource} = radarAndCloudOb
前言: 项目中的问题首先是没有缓存时进入页面时,点击切换等功能造成的, 这个时候谷歌浏览器中的network中会无缓存请求图片所在的地址,短则1s 长则2s,对用户感觉不是很友好。于是乎第一种解决方法出来了:图片预加载,尽管它仍然有局限性;
图片预加载
let {radarImgArray,radarSource,cloudImgArray,cloudSource} = radarAndCloudObj
let images = new Array();
for (let i = radarImgArray.length - 1; i >= 0; i--) {
images[i] = new Image();
images[i].src = radarSource + radarImgArray[i];
images[i].onload = () => {
if (i == 19) {
this.drawradarToMap(this.radarIndex);
}
};
}
for(let i = 0; i < cloudImgArray.length-1;i++){
images[i] = new Image();
images[i].src = cloudSource + cloudImgArray[i];
images[i].onload = () => {
// console.log(name +'=========>' + i);
if (i == 19) {
this.mess.close();
const h = this.$createElement;
Message({
message: h("p", null, [
h("span", { style: "color:#42f0ff" }, "数据加载完成 "),
]),
duration: 3000,
offset: 150,
type: "success",
customClass: "messageClass",
});
}
};
}
好了,上面的代码不用看那么多,讲述一下思路,在你创建一个Image对象并赋予这个image对象的src属性的时候,你可以查看一下network,你会发现他一直在加载。每一张加载完之后都会调用一个onload回调,我们可以在这里面做一些事。
- 但同时你应该也已经发现了:由于使用的是for循环,一旦轮到这个函数实现的时候,异步队列中顺延下去,我的总共是40张图片需要在一开始加载,这会你在别的地方调用网络请求的时候,你会发现怎么也请求不到,原因很简单,排上了队,但是没轮到它;
交代完前情,我开始考虑第二种去实现这个图片加载的方法,这里想要解决的痛点只有一个,不管他加载与否,只要我想调用别的网络请求就能顺畅响应,一句话来说:我想让我在操作的时候的优先级是最高的。
第一时间想到线程相关,假如我可以一个线程去跑这个加载的任务,不会影响到我主线程的任何操作,可是js是单线程的语言,怎么实现多线程的操作呢?一顿搜索:使用web worker;
web workers 介绍
关注上面重点,使用web workers 可以独立于主线程做到运行一个脚本。 注意:worker将运行在于当前window不同的另一个全局上下文中,意味着:你不应该尝试在这里面去操作dom元素,同时Image对象在这里也是找不到的。因为它依赖于window 对象。
照这么解释,使用web workers实现图片预加载这条路就行不通了?
不,可以!但我们也许得试着不从new Image,设置src方向入手。总之我们的核心就是让这个图片在子线程中请求,一般请求我们都是使用ajax,那试试吧。
首先先处理兼容问题,假如用户浏览器版本太低级,你可以使用第一种,反正我是写了两种。
let worker;
if(Worker !== "undefined"){
if(typeof worker == "undefined"){
worker = new Worker("js在本地的路径");
}
}
上面就完成了如下事情: 独立于主线程运行一个脚本;
接着 通过ajax 请求 地址,把它返回的类型设置为blob类型;
参考代码:
/**
* 这个js 文件 主要用于 给主线程 跑这 一个 极其需要使用浏览器资源的图片预加载
* preloadByRadar.js
*/
onmessage = function (radarAndCloudObj) {
let data = radarAndCloudObj.data;
let radarUrlArray = []
for (let i = data.radarImgArray.length - 1; i >= 0; i--) {
let req = new XMLHttpRequest();
let image = data.radarSource + data.radarImgArray[i];
req.open('GET', image, false);
req.responseType = 'blob';
req.onreadystatechange = function () {
if (req.readyState == 4) {
radarUrlArray.push(req.response);
self.postMessage(req.response);
if (i == 19) {
self.postMessage('loadRadarToMap');
}
}
}
req.send(null);
}
self.postMessage([radarUrlArray,'radar']);
}
这里ajax我使用了同步请求,没啥,业务需求,异步怕自己把握不住。 将请求的响应类型设置为blob,然后将请求图片得到的二进制流发送至主线程。
onmessage(callback)
简单解释下:接收消息,在子线程中onmessage 接收主线程的postMessage 发送过来的消息,反之,主线程worker的onmessage也可以接受到子线程发送的消息。postmessage()
第一个参数是对象类型的,可以被发送到线程的。
主线程中的函数调用 项目中完整的调用例子
/**
* 雷达图 和 云图的 预加载
* @param {Object} radarAndCloudObj 顾名思义,包含雷达相关属性 跟云图相关属性的对象
*/
radarAndCloudImgPreload: function (radarAndCloudObj) {
let radarWorker, cloudWorker;
let _this = this;
// 如果用户的浏览器 版本 支持 worker 就使用更好的方式 去执行预加载
if (typeof Worker !== "undefined") {
// 这里也许未来 还可以做 优化、 就是在开启一个线程 分别加载
// 由于我现在使用的方法是同步的ajax 请求 、 其实并不够完美、
// 应该是: 一个线程加载雷达图 一个线程加载云图 这样子 应该性能会更卓越。
//后面测过了,实际上差不多。
// 当然 日后有空 可以进行修改。
if (typeof radarWorker == "undefined") {
radarWorker = new Worker("js/preload/preloadByRadar.js");
cloudWorker = new Worker("js/preload/preloadByCloud.js");
}
// 给子线程发送 数据
radarWorker.postMessage(radarAndCloudObj);
cloudWorker.postMessage(radarAndCloudObj);
// 创建 读取二进制流后的url
var createObjectURL = function (blob) {
return window[window.webkitURL ? "webkitURL" : "URL"][
"createObjectURL"
](blob);
};
// 监听雷达图 子线程发送的消息
radarWorker.onmessage = function (status) {
let retData = status.data;
if (retData == "loadRadarToMap") {
// 这里我觉得 不应该去做改动
// 原因如下: 更优秀的编码的价值 不高于修改的价值。
// 一句话来说 不够优雅~
_this.drawradarToMap(_this.radarIndex);
} else if (retData instanceof Array && retData.length == 2) {
// 处理bloburls 将原本通过拼接请求地址的数组 转换成已经被处理过的请求数组
_this.blobRadarUrls = retData[0];
_this.blobRadarUrls.map((item, index, arr) => {
var newimgdata = createObjectURL(item);
arr[index] = newimgdata;
});
console.log(retData);
}
};
// 监听云图 子线程发送的消息
cloudWorker.onmessage = function (status) {
let retData = status.data;
if (retData == "allPictureInit") {
// 全部加载完了 ,将原本的提示信息 关闭 , 同时提示数据加载完成
_this.mess.close();
const h = _this.$createElement;
Message({
message: h("p", null, [
h("span", { style: "color:#42f0ff" }, "数据加载完成 "),
]),
duration: 3000,
offset: 150,
type: "success",
customClass: "messageClass",
});
} else if (retData instanceof Array && retData.length == 2) {
// 处理bloburls 将原本通过拼接请求地址的数组 转换成已经被处理过的请求数组
_this.blobCloudUrls = retData[0];
_this.blobCloudUrls.map((item, index, arr) => {
var newimgdata = createObjectURL(item);
arr[index] = newimgdata;
});
// 表示数组已经转换完毕 我们下次点击之后 就使用该数组
_this.blobInit = true;
}
};
}
}
let radarAndCloudObj = {
radarImgArray: this.radarImages,
radarSource: "/api/data/radarpic/",
cloudImgArray: this.cloudImgArray,
cloudSource: "/api/data/cloud/IR1/",
};
讲述下上面代码的核心逻辑:
1、首先主线程运行一个独立于主线程的脚本;
2、接着给这个脚本(子线程)发送数据,我这里后端给我返回的是图片地址的后缀,我得把前面的路径也一起发送过去。
3、把子线程中ajax请求后发送的消息监听到,一个arr,长度为2,当然这里你也可以自己去修改,重点是监听到这个图片数据已经请求完了。
4、将设置在组件中的bloburls 处理,请求的是二进制流,我们需要的是一个可供我们展示的地址
5、当全部加载完后,发送一个字符串"allPictureInit"表示加载完成。同时把blobInit设置为true,表示通过我们请求的二进制流转换成的与当前页面相关的domString已经全部可以使用(ps: 你也可以每监听到一张图片被加载,就转换原本的请求地址。看实际需求)。
调用实例
// 将设置 imgsource 的公共逻辑抽离出来
changeCloudImgSource: function (index) {
this.cloudImg = this.blobInit
? this.blobCloudUrls[index]
: "http://x.x.x.x/data/cloud/IR1/" + this.cloudImgArray[index];
},
效果
第一张图片中展示的是:主线程 是 独立于子线程的,无论我在主线程做什么操作,他都会以主线程为主,子线程只有在主线程空闲时才会进行加载。
第二张图片展示的是: 使用这个经过二进制流转换的url,加载是近乎不需要任何时间的。
更多推荐
所有评论(0)