一、關於延遲載入
圖片的延遲載入,是 APP 裡常用的一種技術,圖片首先會生成一張預覽圖,等到原圖下載完成後,再替換掉。 如下面二張圖所示。實現起來,雖然不是很難,但要把它封裝好,也不大容易。在這裡主要講解一下 ChiTuStore 專案中是如何封裝。
二、圖片的繫結
我們開啟 App/Module/Home/Index.html 檔案,可以找到下面一段程式碼,這段程式碼是用來對首頁產品列表進行繫結的,我要關注的是 <img data-bind="attr:{src:ImagePath}" class="img-responsive-70"> 。
這段話用來對圖片進行繫結的。
<div data-bind="foreach:homeProducts,visible:ko.unwrap(homeProducts).length > 0" class="row products" style="margin:0px;"> <div class="col-xs-6 text-center item"> <a data-bind="attr:{href:'#Home_Product_'+ProductId}" href="product.html"> <img data-bind="attr:{src:ImagePath}" class="img-responsive-70"> <div class="bottom"> <div class="interception" data-bind="html:Name"></div> <div> <div class="price pull-left" data-bind="money:Price"></div> <promotion-label params="value:PromotionLabel" class="pull-right"></promotion-label> </div> </div> </a> </div> </div>
三、重寫 ko attr 繫結
因為圖片的繫結,是使用 ko 的 attr 繫結來呈現的,我們需要對其重寫。來實現我們預覽圖的生成,原圖的下載及替換。開啟 App/Core/ko.ext.ts 檔案,這個檔案是用來對 ko 進行擴充套件的。、
我們可以在檔案中找到下面的程式碼,這程式碼程式碼就是用來重寫 ko attr 繫結的。
var _attr = ko.bindingHandlers.attr; ko.bindingHandlers.attr = (function () { return { 'update': function (element, valueAccessor, allBindings) { if (element.tagName == 'IMG') {var value = ko.utils.unwrapObservable(valueAccessor()) || {}; ko.utils['objectForEach'](value, function (attrName, attrValue) { var src = ko.unwrap(attrValue); if (attrName != 'src' || !src) return true; //========================================================== // 說明:替換圖片路徑 var match = src.match(/_\d+_\d+/); if (match && match.length > 0) { var arr = match[0].split('_'); var img_width = new Number(arr[1]).valueOf(); var img_height = new Number(arr[2]).valueOf(); $(element).attr('width', img_width + 'px'); $(element).attr('height', img_height + 'px'); var src_replace src_replace = getPreviewImage(img_width, img_height); valueAccessor = $.proxy(function () { var obj = ko.utils.unwrapObservable(this._source()); var src = ko.unwrap(obj.src); obj.src = this._src; var img_node = this._element; var image = new Image(); image.onload = function () { img_node.src = this.src; } image.src = getImageUrl(src); return obj; }, { _source: valueAccessor, _src: src_replace, _element: element }); } else { value.src = src; valueAccessor = $.proxy(function () { return this._value; }, { _value: value }); } }); } return _attr.update(element, valueAccessor, allBindings); } } })();
我們先來看第一句:
var _attr = ko.bindingHandlers.attr;
這句話是把 ko 原來的 attr 繫結賦值到 _attr 上,因為我們還需要用到它原來的 attr 繫結。
我們繼續看,這段程式碼是說,我們只需要處理 IMG 元素就好了,其它,還是按原來處理吧,看,_attr (就是原來的處理函式)用到了吧。
if (element.tagName == 'IMG') { // 處理圖片,程式碼已省略掉 } return _attr.update(element, valueAccessor, allBindings);
這段話可能有點難懂。要生預覽圖,需要圖片檔案的名稱按約定規命名,例如:17838fdb3e704178938cfb1a73556798_360_360.jpeg,其中的 360_360 分別是圖片的寬和高,也就是說,我們必須知道圖片的寬高才能生成預覽圖,如果不清楚,是無法生成的。下面程式碼中的正規表達,就是用來判斷這個的,檔名符合規定的,才生成預覽圖,否則按常規的來處理。
var match = src.match(/_\d+_\d+/); if (match && match.length > 0) { //..... }else{ value.src = src; valueAccessor = $.proxy(function () { return this._value; }, { _value: value }); }
三、預覽圖的生成
我們在程式碼中可以找到 getPreviewImage 函式,下面我們看看這個函式。這裡又用到了 canvas。也就是說,如果瀏覽器不支援 canvas ,是無法生成預覽圖的。程式碼並不難理解,就不一一細說了。
function getPreviewImage(img_width, img_height) { var scale = (img_height / img_width).toFixed(2); var img_name = 'img_log' + scale; var img_src = localStorage.getItem(img_name); if (img_src) return img_src; var MAX_WIDTH = 320; var width = MAX_WIDTH; var height = width * new Number(scale).valueOf(); var canvas = document.createElement('canvas'); canvas.width = width; //img_width; canvas.height = height; //img_height; var ctx = canvas.getContext('2d'); ctx.fillStyle = 'whitesmoke'; ctx.fillRect(0, 0, canvas.width, canvas.height); // 設定字型 ctx.font = "Bold 40px Arial"; // 設定對齊方式 ctx.textAlign = "left"; // 設定填充顏色 ctx.fillStyle = "#999"; // 設定字型內容,以及在畫布上的位置 ctx.fillText(site.config.storeName, canvas.width / 2 - 75, canvas.height / 2); img_src = canvas.toDataURL('image/png'); localStorage.setItem(img_name, img_src); return img_src; }
四、小結
雖然實現了預覽圖的生成,但是,還不足夠好,因為還有一個很重要的功能是沒有實現,就是隻顯示可見區域的圖片。另外:有朋友可能會問,我們伺服器上的圖片名稱不符合這裡的規範,咋辦?讓你們服務端的同事改唄。^_^
當然,如果你有更好的辦法,也可以告訴我。