首页 web前端 js教程 与承诺并行预加载图像

与承诺并行预加载图像

Feb 19, 2025 am 11:01 AM

Preloading Images in Parallel with Promises

核心要点

  • 使用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中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

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

热门文章

<🎜>:泡泡胶模拟器无穷大 - 如何获取和使用皇家钥匙
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系统,解释
3 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

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

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

热门话题

Java教程
1664
14
CakePHP 教程
1423
52
Laravel 教程
1318
25
PHP教程
1269
29
C# 教程
1248
24
JavaScript引擎:比较实施 JavaScript引擎:比较实施 Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

Python vs. JavaScript:学习曲线和易用性 Python vs. JavaScript:学习曲线和易用性 Apr 16, 2025 am 12:12 AM

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

JavaScript:探索网络语言的多功能性 JavaScript:探索网络语言的多功能性 Apr 11, 2025 am 12:01 AM

JavaScript是现代Web开发的核心语言,因其多样性和灵活性而广泛应用。1)前端开发:通过DOM操作和现代框架(如React、Vue.js、Angular)构建动态网页和单页面应用。2)服务器端开发:Node.js利用非阻塞I/O模型处理高并发和实时应用。3)移动和桌面应用开发:通过ReactNative和Electron实现跨平台开发,提高开发效率。

如何使用Next.js(前端集成)构建多租户SaaS应用程序 如何使用Next.js(前端集成)构建多租户SaaS应用程序 Apr 11, 2025 am 08:22 AM

本文展示了与许可证确保的后端的前端集成,并使用Next.js构建功能性Edtech SaaS应用程序。 前端获取用户权限以控制UI的可见性并确保API要求遵守角色库

使用Next.js(后端集成)构建多租户SaaS应用程序 使用Next.js(后端集成)构建多租户SaaS应用程序 Apr 11, 2025 am 08:23 AM

我使用您的日常技术工具构建了功能性的多租户SaaS应用程序(一个Edtech应用程序),您可以做同样的事情。 首先,什么是多租户SaaS应用程序? 多租户SaaS应用程序可让您从唱歌中为多个客户提供服务

从C/C到JavaScript:所有工作方式 从C/C到JavaScript:所有工作方式 Apr 14, 2025 am 12:05 AM

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

JavaScript和Web:核心功能和用例 JavaScript和Web:核心功能和用例 Apr 18, 2025 am 12:19 AM

JavaScript在Web开发中的主要用途包括客户端交互、表单验证和异步通信。1)通过DOM操作实现动态内容更新和用户交互;2)在用户提交数据前进行客户端验证,提高用户体验;3)通过AJAX技术实现与服务器的无刷新通信。

JavaScript在行动中:现实世界中的示例和项目 JavaScript在行动中:现实世界中的示例和项目 Apr 19, 2025 am 12:13 AM

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。

See all articles