spring 并行加载
本文的主题实际上是非常具体的。 最近,我遇到了需要并行预加载很多图像的情况。 在给定的限制条件下,它最终比最初预期的更具挑战性,我在此过程中当然学到了很多东西。 但是首先,让我在开始之前不久描述情况。
假设我们在页面上有一些“甲板”。 从广义上讲,甲板是图像的集合。 我们想要预加载每个卡座的图像,并能够知道卡座何时完成加载其所有图像。 在这一点上,我们可以自由地运行我们想要的任何代码,例如将一个类添加到平台,运行图像序列,记录一些东西等等。
首先,听起来很简单。 听起来甚至很容易。 尽管也许您像我一样忽略了一个细节:我们希望所有卡座并行而不是按顺序加载。 换句话说,我们不想加载来自卡座1的所有图像,然后加载来自卡座2的所有图像,然后加载来自卡座3的所有图像,依此类推。
确实,这不是理想的,因为我们最终不得不让甲板等待之前的甲板结束。 因此,在第一个甲板上有几十个图像,而第二个甲板上只有一个或两个图像的情况下,我们必须等待第一个甲板上的图像完全装满,然后才能准备好第2甲板上的图像。 当然,我们可以做得更好!
因此,我们的想法是并行加载所有平台,以便在平台完全加载后,我们不必等待其他平台。 为此,大致要点是先加载所有卡座的第一张图像,然后加载所有卡座的第二张图像,依此类推,直到所有图像都已预加载。
好吧,让我们从创建一些标记开始,以便我们都对发生的事情表示同意。
顺便说一句,在本文中,我将假定您熟悉诺言的概念。 如果不是这种情况,我建议您阅读一下 。
标记
在标记方面,甲板只是一个元素,例如div
,它带有一个deck
类,因此我们可以将其作为目标,而data-images
属性则包含一个图像URL数组(如JSON)。
<div class="deck" data-images='["...", "...", "..."]'>...</div>
<div class="deck" data-images='["...", "..."]'>...</div>
<div class="deck" data-images='["...", "...", "...", "..."]'>...</div>
准备地面
在JavaScript方面,这毫无疑问是更为复杂的。 我们将构建两种不同的东西:甲板类 (请在非常大的引号之间放置该类,并且不要对术语进行挑剔),以及一个预加载器工具。
因为预加载器必须知道所有平台上的所有图像才能以特定顺序加载它们,所以需要在所有平台上共享它。 甲板不能拥有自己的预加载器,否则我们将面临最初的问题:代码是按顺序执行的,这不是我们想要的。
因此,我们需要将预载器传递到每个平台。 后者将其图像添加到预加载器的队列中,一旦所有平台都将其项目添加到队列中,预加载器就可以开始预加载。
执行代码段为:
// Instantiate a preloader
var ip = new ImagePreloader();
// Grab all decks from the DOM
var decks = document.querySelectorAll('.deck');
// Iterate over them and instantiate a new deck for each of them, passing the
// preloader to each of them so that the deck can add its images to the queue
Array.prototype.slice.call(decks).forEach(function (deck) {
new Deck(deck, ip);
});
// Once all decks have added their items to the queue, preload everything
ip.preload();
我希望到目前为止,这是有道理的!
建造甲板
根据您要对平台进行的操作,“课程”可能会很长。 对于我们的场景,我们唯一要做的就是在节点的图像完成加载后将已loaded
类添加到节点。
Deck
函数没有太多要做:
- 加载数据(来自
data-images
属性) - 将数据追加到预加载器队列的末尾
- 告诉预加载器数据预加载后该怎么办
var Deck = function (node, preloader) {
// We get and parse the data from the `data-images` attribute
var data = JSON.parse(node.getAttribute('data-images'));
// We call the `queue` method from the preloader, passing it the data and a
// callback function
preloader.queue(data, function () {
node.classList.add('loaded');
});
};
到目前为止进展顺利,不是吗? 剩下的只有预加载器,尽管它也是本文中最复杂的代码。
构建预加载器
我们已经知道我们的预加载器需要一个queue
方法来将图像集合添加到队列中,并且需要一个preload
方法来启动预加载。 它还需要一个帮助函数来预加载图像,称为preloadImage
。 让我们开始:
var ImagePreloader = function () { ... };
ImagePreloader.prototype.queue = function () { ... }
ImagePreloader.prototype.preloadImage = function () { ... }
ImagePreloader.prototype.preload = function () { ... }
预加载器需要一个内部队列属性来保存必须预加载的卡座以及它们各自的回调。
var ImagePreloader = function () {
this.items = [];
}
items
是一个对象数组,其中每个对象都有两个键:
-
collection
其中包含要预加载的图像网址数组, - 包含甲板完全加载时要执行的函数的
callback
。
知道了这一点,我们可以编写queue
方法。
// Empty function in case no callback is being specified
function noop() {}
ImagePreloader.prototype.queue = function (array, callback) {
this.items.push({
collection: array,
// If no callback, we push a no-op (empty) function
callback: callback || noop
});
};
![](https://i-blog.csdnimg.cn/blog_migrate/9709e2f4ae2b71ffca72eb4921c17aca.png)
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
好的。 此时,每个平台都可以将其图像附加到队列中。 现在,我们必须构建preload
方法,该方法将负责实际预加载图像。 但是在开始编写代码之前,让我们退后一步来了解我们需要做什么。
这样做的想法不是要一个接一个地预加载每个卡座中的所有图像。 这个想法是预加载每个卡座的第一个图像,然后预加载第二个,然后第三个,依此类推。
预加载图像意味着从JavaScript创建新图像(使用new Image()
)并对其应用src
。 这将促使浏览器异步加载源。 由于此异步过程,我们需要注册一个Promise,该Promise将在浏览器下载资源后解决。
基本上,我们将用一个承诺替换数组中的每个图像URL,该承诺将在浏览器加载给定图像后解析。 在这一点上,我们将能够使用Promise.all(..)
拥有最终承诺,当来自数组的所有承诺都解决时,最终承诺就解决。 而且,对于每个甲板。
让我们从preloadImage
方法开始:
ImagePreloader.prototype.preloadImage = function (path) {
return new Promise(function (resolve, reject) {
// Create a new image from JavaScript
var image = new Image();
// Bind an event listener on the load to call the `resolve` function
image.onload = resolve;
// If the image fails to be downloaded, we don't want the whole system
// to collapse so we `resolve` instead of `reject`, even on error
image.onerror = resolve;
// Apply the path as `src` to the image so that the browser fetches it
image.src = path;
});
};
现在, preload
方法。 它可以做两件事(因此可以分为两个不同的功能,但这超出了本文的范围):
- 它将以特定顺序用promise替换所有图像URL(每个卡座中的第一张图像,然后是第二张,然后是第三张…)
- 对于每个平台,它都会注册一个promise,当平台中的所有promise都已解决时,它会调用平台中的回调(!)
ImagePreloader.prototype.preload = function () {
// Promises are not supported, let's leave
if (!('Promise' in window)) {
return;
}
// Get the length of the biggest deck
var max = Math.max.apply(Math, this.items.map(function (el) {
return el.collection.length;
}));
// Loop from 0 to the length of the largest deck
for (var i = 0; i < max; i++) {
// Iterate over the decks
this.items.forEach(function (item) {
// If the deck is over yet, do nothing, else replace the image at
// current index (i) with a promise that will resolve when the image
// gets downloaded by the browser.
if (typeof item.collection[i] !== 'undefined') {
item.collection[i] = this.preloadImage(item.collection[i])
}
}, this);
}
// Iterate over the decks
this.items.forEach(function (item, index) {
// When all images from the deck have been fetched by the browser
Promise.all(item.collection)
// Execute the callback
.then(function () { item.callback() })
.catch(function (err) { console.log(err) });
});
};
而已! 毕竟不是那么复杂,你同意吗?
进一步推进
尽管使用回调函数告诉预加载器在加载卡座时应执行的操作不是很好,但该代码仍然运行良好。 您可能要使用Promise而不是回调,尤其是因为我们一直都使用Promises!
我不确定如何解决这个问题,所以我必须同意我请我的朋友ValérianGalliat帮助我解决这个问题。
我们在这里使用的是延期承诺 。 延迟的承诺不是本地Promise API的一部分,因此我们需要对其进行填充。 值得庆幸的是,这仅需几行。 基本上,延迟的承诺是一个承诺,稍后您可以解决。
将其应用到我们的代码中,它只会改变很少的事情。 首先使用.queue(..)
方法:
ImagePreloader.prototype.queue = function (array) {
var d = defer();
this.items.push({
collection: array,
deferred: d
});
return d.promise;
};
.preload(..)
方法中的分辨率:
this.items.forEach(function (item) {
Promise.all(item.collection)
.then(function () { item.deferred.resolve() })
.catch(console.log.bind(console));
});
最后是我们将数据添加到队列的方式!
preloader.queue(data)
.then(function () {
node.classList.add('loaded');
})
.catch(console.error.bind(console));
我们完成了!
如果您想查看实际的代码,请看下面的演示:
请参阅CodePen上的SitePoint( @SitePoint )提供的Pen QjjGaL 。
结论
你们在那里去乡亲。 在大约70行JavaScript中,我们设法异步地并行加载来自不同集合的图像,并在完成集合加载后执行一些代码。
从那里,我们可以做很多事情。 就我而言,关键是单击按钮时将这些图像作为快速循环序列(gif样式)运行。 因此,我在加载过程中禁用了该按钮,并在甲板完成预加载所有图像后将其启用。 因此,由于浏览器已经缓存了所有图像,因此第一个循环运行将无缝进行。
我希望你喜欢它! 您可以在GitHub上查看代码,也可以直接在CodePen上使用它。
翻译自: https://www.sitepoint.com/preloading-images-in-parallel-with-promises/
spring 并行加载