核心要點
Promise.all()
方法為每個組創建一個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中文網其他相關文章!