核心要点
- 使用Promise异步加载图片,允许同时加载不同图片集合,并在集合加载完成后执行代码。这通过减少整体加载时间来显着提高网站性能。
- 此技术涉及为所有图片“组”(集合)创建一个共享预加载器,该预加载器将要加载的图片排队。然后,预加载器并行(而非顺序)开始加载图片,避免必须等待一个组完成才能开始下一个组。
- 每个图片URL都替换为一个Promise,该Promise在浏览器加载图片后解析。然后,可以使用
Promise.all()
方法为每个组创建一个Promise,该Promise在数组中的所有Promise都解析后解析。 - 通过使用延迟Promise而不是回调来告诉预加载器在组加载完成后该做什么,可以进一步改进该技术。这允许稍后控制Promise的解析。
本文探讨一个具体问题:如何并行预加载大量图片。 我最近遇到了这个问题,发现它比最初预期的更具挑战性,也从中学习了很多。首先,让我简要描述一下场景。假设页面上有几个“组”。广义上说,一个组就是一个图片集合。我们希望预加载每个组的图片,并能够知道何时完成某个组的图片加载。此时,我们可以自由运行任何我们想要的代码,例如向组添加一个类、运行图像序列、记录某些内容等等。起初,这听起来很简单,甚至非常简单。但是,你可能和我一样忽略了一个细节:我们希望所有组并行加载,而不是顺序加载。换句话说,我们不希望先加载组1的所有图片,然后加载组2的所有图片,再加载组3的所有图片,依此类推。事实上,这不是理想的,因为最终会有一些组需要等待前面的组完成。因此,在一个场景中,如果第一个组有几十张图片,而第二个组只有一两张图片,我们就必须等待第一个组完全加载才能准备第二个组。这不好。我们肯定可以做得更好!所以我们的想法是并行加载所有组,这样当一个组完全加载时,我们不必等待其他组。为此,大致思路是加载所有组的第一张图片,然后加载所有组的第二张图片,依此类推,直到所有图片都已预加载。好了,让我们从创建一些标记开始,这样我们就能就正在发生的事情达成一致。
顺便说一句,在本文中,我假设您熟悉Promise的概念。如果不是这样,我建议您阅读这篇文章。
标记
从标记的角度来看,一个组只不过是一个元素(例如div),带有deck类以便我们可以定位它,以及一个包含图片URL数组(作为JSON)的data-images属性。
<div class="deck" data-images='["...", "...", "..."]'>...</div> <div class="deck" data-images='["...", "..."]'>...</div> <div class="deck" data-images='["...", "...", "...", "..."]'>...</div>
准备工作
在JavaScript方面,这——不出所料——有点复杂。我们将构建两样不同的东西:一个组类(请将此放在非常大的引号之间,不要对术语吹毛求疵)和一个预加载器工具。因为预加载器必须知道所有组的所有图片才能以特定的顺序加载它们,所以它需要在所有组之间共享。一个组不能有它自己的预加载器,否则我们会遇到最初的问题:代码是顺序执行的,这不是我们想要的。所以我们需要一个传递给每个组的预加载器。后者将它的图片添加到预加载器的队列中,一旦所有组都将它们的项目添加到队列中,预加载器就可以开始预加载。执行代码片段如下:
// 实例化一个预加载器 var ip = new ImagePreloader(); // 从DOM获取所有组 var decks = document.querySelectorAll('.deck'); // 遍历它们并为每个组实例化一个新的组,将预加载器传递给每个组,以便组可以将它的图片添加到队列中 Array.prototype.slice.call(decks).forEach(function (deck) { new Deck(deck, ip); }); // 一旦所有组都将它们的项目添加到队列中,就预加载所有内容 ip.preload();
我希望到目前为止,这是有意义的!
构建组
根据您想对组做什么,这个“类”可能相当长。对于我们的场景,我们唯一要做的事情是在其图片加载完成后向节点添加一个loaded类。Deck函数没有太多工作要做:1. 加载数据(从data-images属性);2. 将数据添加到预加载器队列的末尾;3. 告诉预加载器在数据预加载完成后该做什么。
var Deck = function (node, preloader) { // 我们从`data-images`属性获取并解析数据 var data = JSON.parse(node.getAttribute('data-images')); // 我们调用预加载器的`queue`方法,将数据和回调函数传递给它 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 () { ... }
预加载器需要一个内部queue属性来保存它必须预加载的组,以及它们各自的回调。
var ImagePreloader = function () { this.items = []; }
items是一个对象数组,其中每个对象有两个键:- collection包含要预加载的图片URL数组;- callback包含在组完全加载后要执行的函数。
知道了这一点,我们可以编写queue方法。
<div class="deck" data-images='["...", "...", "..."]'>...</div> <div class="deck" data-images='["...", "..."]'>...</div> <div class="deck" data-images='["...", "...", "...", "..."]'>...</div>
好了。此时,每个组都可以将它的图片添加到队列中。我们现在必须构建preload方法,它将负责实际预加载图片。但在跳转到代码之前,让我们退一步来理解我们需要做什么。我们的想法不是一个接一个地预加载每个组的所有图片。我们的想法是预加载每个组的第一张图片,然后是第二张,然后是第三张,依此类推。预加载一张图片意味着使用JavaScript(使用new Image())创建一个新的图片,并为其应用一个src。这将提示浏览器异步加载源。由于这个异步过程,我们需要注册一个Promise,该Promise在浏览器下载资源后解析。基本上,我们将用一个Promise替换我们数组中的每个图片URL,该Promise在浏览器加载给定图片后解析。此时,我们将能够使用Promise.all(..) 来获得一个最终的Promise,该Promise在数组中的所有Promise都解析后解析。对于每个组都是如此。让我们从preloadImage方法开始:
// 实例化一个预加载器 var ip = new ImagePreloader(); // 从DOM获取所有组 var decks = document.querySelectorAll('.deck'); // 遍历它们并为每个组实例化一个新的组,将预加载器传递给每个组,以便组可以将它的图片添加到队列中 Array.prototype.slice.call(decks).forEach(function (deck) { new Deck(deck, ip); }); // 一旦所有组都将它们的项目添加到队列中,就预加载所有内容 ip.preload();
现在是preload方法。它做两件事(因此可能可以拆分成两个不同的函数,但这不在本文的范围内):1. 它以特定的顺序(每个组的第一张图片,然后是第二张,然后是第三张……)将所有图片URL替换为Promise;2. 对于每个组,它注册一个Promise,当组中的所有Promise都解析后(!)调用组的回调。
var Deck = function (node, preloader) { // 我们从`data-images`属性获取并解析数据 var data = JSON.parse(node.getAttribute('data-images')); // 我们调用预加载器的`queue`方法,将数据和回调函数传递给它 preloader.queue(data, function () { node.classList.add('loaded'); }); };
就是这样!毕竟没有那么复杂,你同意吗?
进一步推进
代码运行良好,尽管使用回调来告诉预加载器在组加载完成后该做什么并不是很优雅。您可能希望使用Promise而不是回调,尤其是在我们一直使用Promise的情况下!我不确定如何解决这个问题,所以我不得不承认我请我的朋友Valérian Galliat帮我解决这个问题。我们在这里使用的是延迟Promise。延迟Promise不是原生Promise API的一部分,因此我们需要为其添加polyfill;谢天谢地,这只需要几行代码。基本上,延迟Promise是一个稍后可以解析的Promise。将其应用于我们的代码,只会改变很少的东西。首先是.queue(..)
方法:
var ImagePreloader = function () { ... }; ImagePreloader.prototype.queue = function () { ... } ImagePreloader.prototype.preloadImage = function () { ... } ImagePreloader.prototype.preload = function () { ... }
.preload(..)
方法中的解析:
var ImagePreloader = function () { this.items = []; }
当然,最后是我们添加数据到队列的方式!
// 如果没有指定回调,则为空函数 function noop() {} ImagePreloader.prototype.queue = function (array, callback) { this.items.push({ collection: array, // 如果没有回调,我们推送一个no-op(空)函数 callback: callback || noop }); };
我们完成了!如果您想查看代码的实际运行情况,请查看下面的演示:(此处应插入CodePen演示链接,因为我无法直接嵌入CodePen)
结论
好了,朋友们。大约70行JavaScript代码,我们就成功地异步并行加载了不同集合中的图片,并在集合加载完成后执行了一些代码。从这里开始,我们可以做很多事情。在我的例子中,重点是在点击按钮时将这些图片作为快速循环序列(gif样式)运行。因此,我在加载期间禁用了按钮,并在组完成所有图片的预加载后重新启用它。由于浏览器已经缓存了所有图片,因此第一个循环运行非常流畅。我希望你喜欢它!您可以在GitHub上查看代码,也可以直接在CodePen上使用它。(此处应插入GitHub链接和CodePen链接)
(此处应添加FAQ部分,与输入文本中的FAQ部分内容一致,但语言表达上进行了一些调整和润色。)
以上是与承诺并行预加载图像的详细内容。更多信息请关注PHP中文网其他相关文章!

Python和JavaScript的未来趋势包括:1.Python将巩固在科学计算和AI领域的地位,2.JavaScript将推动Web技术发展,3.跨平台开发将成为热门,4.性能优化将是重点。两者都将继续在各自领域扩展应用场景,并在性能上有更多突破。

Python和JavaScript在开发环境上的选择都很重要。1)Python的开发环境包括PyCharm、JupyterNotebook和Anaconda,适合数据科学和快速原型开发。2)JavaScript的开发环境包括Node.js、VSCode和Webpack,适用于前端和后端开发。根据项目需求选择合适的工具可以提高开发效率和项目成功率。

是的,JavaScript的引擎核心是用C语言编写的。1)C语言提供了高效性能和底层控制,适合JavaScript引擎的开发。2)以V8引擎为例,其核心用C 编写,结合了C的效率和面向对象特性。3)JavaScript引擎的工作原理包括解析、编译和执行,C语言在这些过程中发挥关键作用。

JavaScript是现代网站的核心,因为它增强了网页的交互性和动态性。1)它允许在不刷新页面的情况下改变内容,2)通过DOMAPI操作网页,3)支持复杂的交互效果如动画和拖放,4)优化性能和最佳实践提高用户体验。

C 和JavaScript通过WebAssembly实现互操作性。1)C 代码编译成WebAssembly模块,引入到JavaScript环境中,增强计算能力。2)在游戏开发中,C 处理物理引擎和图形渲染,JavaScript负责游戏逻辑和用户界面。

JavaScript在网站、移动应用、桌面应用和服务器端编程中均有广泛应用。1)在网站开发中,JavaScript与HTML、CSS一起操作DOM,实现动态效果,并支持如jQuery、React等框架。2)通过ReactNative和Ionic,JavaScript用于开发跨平台移动应用。3)Electron框架使JavaScript能构建桌面应用。4)Node.js让JavaScript在服务器端运行,支持高并发请求。

Python更适合数据科学和自动化,JavaScript更适合前端和全栈开发。1.Python在数据科学和机器学习中表现出色,使用NumPy、Pandas等库进行数据处理和建模。2.Python在自动化和脚本编写方面简洁高效。3.JavaScript在前端开发中不可或缺,用于构建动态网页和单页面应用。4.JavaScript通过Node.js在后端开发中发挥作用,支持全栈开发。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。 1)C 用于解析JavaScript源码并生成抽象语法树。 2)C 负责生成和执行字节码。 3)C 实现JIT编译器,在运行时优化和编译热点代码,显着提高JavaScript的执行效率。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

ZendStudio 13.5.1 Mac
功能强大的PHP集成开发环境

PhpStorm Mac 版本
最新(2018.2.1 )专业的PHP集成开发工具

DVWA
Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

WebStorm Mac版
好用的JavaScript开发工具

SublimeText3汉化版
中文版,非常好用