網頁中的圖片預載入
我們知道在 Web 頁面中實現圖片的預載入其實很簡單,通常的做法是在 JS 中使用 Image
物件即可,程式碼大致如下
1 2 3 4 5 |
var image = new Image() image.onload = function() { console.log('圖片載入完成') } image.src = 'http://misc.360buyimg.com/lib/img/e/logo-201305.png' |
然而在微信小程式(以下簡稱小程式)裡要實現圖片的預載入要更麻煩一些,因為小程式裡並沒有提供類似 Image
這樣的 JS 物件。。
小程式必知必會
在進入正題前,需要了解以下小程式相關的知識(當然最好還是完整的學習一下官方文件):
- 小程式框架的核心是一個響應的資料繫結系統,整個系統分為檢視層和邏輯層兩塊,檢視層即頁面模板(字尾為 .wxml 的檔案),邏輯層即頁面 JS 檔案
- 小程式的頁面模板由一系列的基礎元件組合而成,如
view
、text
、button
等 - 頁面內容的更新基於資料的單向繫結來進行,通過 JS 呼叫 Page 物件的
setData
方法來更新模板中繫結的資料 - 檢視層到邏輯層的通訊是通過事件完成的,在元件中宣告事件的回撥,JS 端可監聽到介面互動的發生、元件狀態的變化等
- 在 WXML 檔案中,可通過
template
進行模板的複用,若template
是在不同檔案裡定義的,需要先通過import
語句進行引入
這裡有個官方的簡單例子可以用來幫助理解
1 2 3 |
<!-- 模板檔案 foo.wxml --> <view> Hello {{name}}! </view> <button bindtap="changeName"> Click me! </button> |
1 2 3 4 5 6 7 8 9 10 11 |
//指令碼檔案 foo.js Page({ data: { name: 'WeChat' }, changeName: function(e) { this.setData({ name: 'MINA' }) } }) |
執行這個頁面會看到一行 Hello WeChat! 的文字及一個按鈕,點選按鈕後文字會變成 Hello MINA!
在小程式中載入圖片
小程式提供一個 image
元件(類似於 HTML 中的 img 標籤),可以設定 src 及載入成功或失敗的回撥,使用起來很簡單
1 2 |
<!-- 模板檔案 bar.wxml --> <image src="http://misc.360buyimg.com/lib/img/e/logo-201305.png" bindload="imageOnLoad" binderror="imageOnLoadError" /> |
1 2 3 4 5 6 7 8 9 |
//指令碼檔案 bar.js Page({ imageOnLoad(ev) { console.log(`圖片載入成功,width: ${ev.detail.width}; height: ${ev.detail.height}`) }, imageOnLoadError() { console.log('圖片載入失敗') } }) |
執行以上程式碼,順利的話頁面上會顯示出一張圖片,同時控制檯會列印出帶圖片寬高的日誌資訊
將功能抽離成公用元件
接下來我們考慮實現這麼一個功能,在頁面上載入一張尺寸和 K 數都很大的圖片,由於圖片很大,下載需要一定的時間,而在這段時間內,使用者看到的是空白或是不完整的圖片,體驗顯然不好。
一種常用的優化手段是先載入一張縮圖,該縮圖通過樣式設定為和原圖一樣的寬高,這樣使用者首先能很快速地看到一張模糊的圖片,此時再去對原圖做預載入,載入完成之後對縮圖進行替換,因為此時圖片已經下載過了,所以介面上能無縫地切換為原圖顯示,效果如下:
完成這個優化操作的關鍵就在於需要一個公共的圖片預載入元件的支援,接下來我們分步驟來看看如何實現
- 新建 demo 頁面及元件相關的檔案 img-loader.js 和 img-loader.wxml,元件需要和頁面一樣有個模板檔案,是因為小程式裡無法動態地插入模板結構。然後在 demo.wxml 裡通過
import
語句引用元件模板,在 demo.js 裡通過 require 語句將元件指令碼進行引入 - 在頁面中通過
template
呼叫元件模板並傳入資料,這裡我們傳遞一個名為imgLoadList
的圖片陣列過去 - 在頁面指令碼中的
onLoad
方法中對元件進行初始化,並將this
物件傳入,因為元件內必須通過 Page 物件的setData
來更新模板裡的內容 - 在元件的 img-loader.js 中定義一個
load
方法用來建立一個圖片的載入,將傳入的src
新增到載入佇列中,並使用setData
方法更新佇列資料 - 接下來在元件 img-loader.wxml 中通過接收到的圖片佇列資料,用
wx:for
指令去生成image
元件來對圖片進行載入,同時將成功及失敗的回撥繫結到 img-loader.js 中的方法中,最終再回撥回 Page 物件中
可以看出,由於小程式裡無法動態地插入模板結構,所以相對於普通網頁端的元件呼叫,這裡多出了在 WXML 檔案中引入及使用模板這個步驟,而其他部分對於呼叫方(即Demo 頁面)來說則是相似的,下面是完整的 Demo 頁面的程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<!-- demo.wxml --> <view class="img_wrap"> <image wx:if="{{ imgUrl }}" src="{{ imgUrl }}" /> </view> <button bindtap="loadImage">Click To Load Image</button> <view class="msg">{{ msg }}</view> <!-- 引入圖片預載入元件 --> <import src="../../img-loader/img-loader.wxml"/> <template is="img-loader" data="{{ imgLoadList }}"></template> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// ------ demo.js ------ //引入圖片預載入元件 const ImgLoader = require('../../img-loader/img-loader.js') //縮圖 80x50 3KB const imgUrlThumbnail = 'http://storage.360buyimg.com/mtd/home/lion1483683731203.jpg' //原圖 3200x2000 1.6MB const imgUrlOriginal = 'http://storage.360buyimg.com/mtd/home/lion1483624894660.jpg' Page({ data: { msg: '', imgUrl: '' }, onLoad() { //初始化圖片預載入元件 this.imgLoader = new ImgLoader(this) }, loadImage() { //載入縮圖 this.setData({ msg: '大圖正拼命載入..', imgUrl: imgUrlThumbnail }) //同時對原圖進行預載入,載入成功後再替換 this.imgLoader.load(imgUrlOriginal, (err, data) => { console.log('圖片載入完成', err, data.src) this.setData({ msg: '大圖載入完成~' }) if (!err) this.setData({ imgUrl: data.src }) }) } }) |
如果把圖片載入完成的回撥統一指定成 Page 物件中的方法,則可以很方便地處理多張圖片的載入,這裡也寫了個例子,效果如下:
總的來說呼叫起來還算方便吧,img-loader 的元件程式碼略多這裡就不貼出來啦,有興趣的同學可以前往 Github 專案頁面 檢視,目前此元件已應用在京東購物小程式版中。Have Fun~