天天看點

自寫的第一個javascript元件滾動加載ScrollLoad開發過程

為什麼要寫這個元件。因為網上已有的滾動加載元件不能滿足我的要求。

我的需求:頁面前端使用的是bootstrap+knockout 開發。前端采用的是MVVM綁定自伺服器中擷取的資料,原來是采用分頁實作的,因為要開發手機可以通路的網頁,是以修改為滾動加載形式,一旦下拉到頁面底部,就用jquery的$.post 加載下一頁。

首先想到的就是在github上擷取别人已經造好的輪子。我先選取的是https://github.com/fa-ge/Scrollload 這個輪子,按照demo操作測試,滑鼠滾動到底部沒有什麼動靜,折騰了兩個多小時,還沒成功,放棄了。然後又選了一個輪子https://github.com/coffeedeveloper/loadmaster ,操作測試仍然不能成功。按照人家自己的demo,不修改任何代碼是可以運作成功的,但是若與我的結合,則不能成功。我分析是因為我的需求特殊,我的是MVVM形式的綁定,不需要append html代碼。這裡無意貶低這兩個滾動加載元件。因為我選取的都是用的人多的。隻能說不适合我的項目,也或者我還不太會用他們寫的輪子解決我的問題。

由于項目進度的問題,我剛開始是直接在頁面中用jquery來實作的,并沒有開發元件。隻是由于用的地方比較多,項目中有四五個頁面在用。而近期項目沒什麼活了。是以萌發了自己開發一個元件的想法。在開發之前當然是惡補元件開發的相關知識。這些包括了javascript閉包,作用域,原型,繼承及面向對象開發的知識。

第一版代碼是這個樣子的scrollload.js:

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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52

//滾動加載閉包寫法

var

ScrollLoad = (

function

() {

var

_range = 80;             

//距下邊界長度/機關px

var

_loaded = 

false

;            

//是否所有資料已加載完

var

_pageIndex = 1;      

var

_loading = 

false

;           

//是否正在加載

var

_totalheight = 0;

function

_Scroll() {

//console.info("a1");

if

(_loaded) 

return

;    

//加載完畢,不在執行scroll函數

//console.info("scroll1");

var

srollPos = $(window).scrollTop();    

//滾動條距頂部距離(頁面超出視窗的高度)

_totalheight = parseFloat($(window).height()) + parseFloat(srollPos);

//console.info("a2");

if

(($(document).height() - _range) <= _totalheight ) {

if

(_loading) 

return

;

_loading = 

true

;

setTimeout(

function

() {

scrollLoad(_pageIndex);  

//此為外部函數調用

//_loading = false;

}, 1000);

}

//console.info("a3");

};

return

function

() {

//console.info(this);

this

.Range = _range;       

this

.Loaded = _loaded;

this

.PageIndex = _pageIndex;

//設定距下邊界長度/機關px

this

.SetRange = 

function

(range) {

_range = range;

};

//設定滾動加載頁碼

this

.SetPageIndex = 

function

(num) {

_pageIndex = num;

};

//設定滾動加載是否已經加載全部資料

this

.SetLoaded = 

function

(loaded) {

_loaded = loaded;

};

//設定_loading為false

this

.SetLoading = 

function

() {

_loading = 

false

;

};

this

.Scroll = 

function

() {

_Scroll();

};

}

})();

第一版主要是解決外部擷取閉包中内部變量問題,比如this.Load擷取閉包中的是否已加載變量_loaded的值。修改内部變量的值問題:比如this.SetPageIndex修改異步調用的頁碼。滾動時執行的内部函數_Scroll()的實作。這裡使用了一個閉包的外部函數,scrollLoad(pageIndex),為什麼要把這個放到閉包内部執行,是因為若放在外部執行,則_Scroll()就要傳回true,或false以便在調用這個閉包元件的時候來判斷是否可以執行,但在實際測試中,滾動加載頁碼變動很快,而且有重複加載的情況,并且容易造成所有要加載的資料已經加載完了,而頁碼還會一直增加的情況。

另一個要解決的問題是采用

 return function(){  

this.Range = _range;       

this.Loaded = _loaded;

this.PageIndex = _pageIndex;

}

這樣的形式傳回可以在外部調用的方法。這種寫法支援像C#調用類執行個體形式用 new 調用

頁面中cshtml的寫法:

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

<

tbody

class

=

"list"

data-bind

=

"with:CustomerVM"

>

<!--ko foreach:$data-->

<

tr

>

<

td

>

<

table

class

=

"table table-bordered  table-striped"

>

<

tbody

>

<

tr

>

<

td

class

=

"nowrap"

>姓名</

td

>

<

td

colspan

=

"5"

>

@*<

p

class

=

"floatLeft margin-right-5"

data-bind

=

"text:IID"

>></

p

>*@

<

p

class

=

"floatLeft margin-right-5"

data-bind

=

"text:Name"

>></

p

>

<

p

class

=

"tc phoneInfo floatLeft margin-right-5"

data-bind

=

"text:Phone"

>></

p

>

<

p

class

=

"thisSort floatLeft margin-right-5"

data-bind

=

"text:SortId"

>></

p

>

</

td

>

</

tr

>

//以下略

//...

</

tbody

>

</

table

>

</

td

>

</

tr

>

<!--/ko-->

</

tbody

>

使用的是knockout的mvvm綁定形式,由于代碼過多,這裡略寫

頁面中相應js代碼:

page.VM.CustomerVM = ko.mapping.fromJS([]);
$(function () {
     
    scrollLoad(1);
});
 
 
function Search(item,e)
{
    scrollLoad(1);
}
 
//滾動加載/
var scroll = new ScrollLoad();
$(window).scroll(function () {
    scroll.Scroll();
});
//console.info(CarouselMock);
//滾動加載/
 
function scrollLoad(pagexx)
{
    //console.info("scrollLoad");
    $.post("/m/operator/GetMyCustomerList", { pageIndex: pagexx, name: $("#name").val(), level1: $("#level1").val(),level2:$("#level2").val() }, function (data) {
    //console.info(data);
    if(pagexx==1)
    {
        ko.mapping.fromJS(data, mappingOption, page.VM.CustomerVM);
         
        //console.info("page1scroll:" + scroll.PageIndex);
    }
    if(pagexx>=2)
    {
        if (data ==null||data== "") {
        scroll.SetLoaded(true);
        return;
        }
        //main.append(data);
        //把viewModel即page.VM.CustomerVM重新轉換回data對象
        var unmapped = ko.mapping.toJS(page.VM.CustomerVM);
        //然後轉換回的data對象與自伺服器讀取的相加
        var newData=unmapped.concat(data);
        //然後再重新轉換為viewmodel對象
        ko.mapping.fromJS(newData, mappingOption, page.VM.CustomerVM);
        //console.info("newdata:"+pagexx);
        console.info(newData);
         
    }
    scroll.SetPageIndex(scroll.PageIndex++);
    scroll.SetLoading();
 
    //類名加載
    //略.....
}
           

page.VM.CustomerVM = ko.mapping.fromJS([]);

此為customerVM knockout綁定初始化

var

scroll = 

new

ScrollLoad();

$(window).scroll(

function

() {

scroll.Scroll();

});

此為自寫的滾動加載元件調用代碼

先用new 執行個體化,然後在window 的 scroll事件中直接執行 scroll.Scroll()。 這裡需要注意的是,此部分代碼無須寫在$(function(){}) 中

function

scrollLoad(pagexx)

此為分頁異步加載函數,pagexx就是頁碼。其他的在$.post部分的則為頁面中的搜尋條件。這個函數在上文已經提到,是我寫的ScrollLoad.js中被調用的一個外部函數。

在scrollLoad函數中,有兩個地方加了對scrollload元件的調用代碼。第一個地方:

if

(data ==

null

||data== 

""

) {

scroll.SetLoaded(

true

);

return

;

}

這個是當自伺服器擷取的data為null,表示已經到了最後一頁,這個時候直接設定_loaded為加載完成。這樣如果再滾動,就不會再進行加載操作。

第二個地方:

scroll.SetPageIndex(scroll.PageIndex++);

scroll.SetLoading();

當異步加載完成,則設定頁碼自增1.然後設定_loading變量為初始的false。表示可以繼續加載其他頁

$(

function

() {

scrollLoad(1);

});

這個代碼是在第一次載入頁面時加載第一頁

function

Search(item,e)

{

scrollLoad(1);

}

而這個代碼則是在點選搜尋按鈕時使用

寫完後對整個頁面進行測試,滾動加載可以正常使用。初步成果了。但是對我寫的第一版是不太滿意的。因為在元件中調用了外部函數,這耦合性太大。然後就進行第二版的開發。解決耦合性問題。這個時候首先想到的就是settimeout方法,因為這個方法是可以把一個函數傳入settimeout中來執行的。于是在網上扒資料,發現javascript對函數傳入參數沒有什麼限制,既可以傳入值,也可以傳入對象,方法等。于是我對我寫的滾動元件進行改造,使其支援外部函數以參數形式傳入。

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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58

//滾動加載閉包寫法

var

ScrollLoad = (

function

() {   

var

_range = 80;             

//距下邊界長度/機關px

var

_loaded = 

false

;            

//是否所有資料已加載完

var

_pageIndex = 1;      

var

_loading = 

false

;           

//是否正在加載

var

_totalheight = 0;

function

_Scroll(callback) {

//console.info("a1");

if

(_loaded) 

return

;    

//加載完畢,不在執行scroll函數

//console.info("scroll1");

var

srollPos = $(window).scrollTop();    

//滾動條距頂部距離(頁面超出視窗的高度)

_totalheight = parseFloat($(window).height()) + parseFloat(srollPos);

//console.info("a2");

if

(($(document).height() - _range) <= _totalheight ) {

if

(_loading) 

return

;

_loading = 

true

;

setTimeout(

function

() {

//console.info(typeof callback);

if

(

typeof

callback !== 

'function'

)

{

throw

new

TypeError(

'"callback" argument must be a Function'

);

}

console.info(_pageIndex);

callback(_pageIndex);  

//此為外部函數調用

//_loading = false;

}, 1000);

}

//console.info("a3");

};

return

function

() {

//console.info(this);

this

.Range = _range;       

this

.Loaded = _loaded;

this

.PageIndex = _pageIndex;

//設定距下邊界長度/機關px

this

.SetRange = 

function

(range) {

_range = range;

};

//設定滾動加載頁碼

this

.SetPageIndex = 

function

(num) {

_pageIndex = num;

};

//設定滾動加載是否已經加載全部資料

this

.SetLoaded = 

function

(loaded) {

_loaded = loaded;

};

//設定_loading為false

this

.SetLoading = 

function

() {

_loading = 

false

;

};

this

.Scroll = 

function

(callback) {

_Scroll(callback);

};

}

})();

這裡對_Scroll函數增加了一個傳入參數callback,并在使用的時候對其類型進行判斷。若判斷為非函數。則直接抛出錯誤。若是函數,則執行callback(_pageIndex); 此行代碼替換了原來需要調用的外部函數:scrollLoad(pagexx) 。而使外部函數scrollLoad以參數的形式可以傳入。這樣外部函數就可以不是scrollLoad這個名字,而可以是其他函數名。更改後,元件的調用方法也做了相應的更改。

1 2 3 4 5

var

scroll = 

new

ScrollLoad();

$(window).scroll(

function

() {

var

callback = 

function

(index) { scrollLoad(index); }

scroll.Scroll(callback);

});

其餘頁面調用不變。

這次元件更改後還是覺得不滿意,因為

$(window).scroll(

function

() {

var

callback = 

function

(index) { scrollLoad(index); }

scroll.Scroll(callback);

});

這種調用導緻代碼過多。打算把這部分代碼移入到元件内部。通過多次嘗試,是可行的。更改時這樣的:

1 2 3

$(window).scroll(

function

() {

_Scroll(callback);

});

這樣改變後,外部對元件調用的代碼更少,可以使用三行代碼實作初始調用:

1 2 3

var

scroll = 

new

ScrollLoad();     

var

callback = 

function

(index) { scrollLoad(index); }

scroll.Scroll(callback);

甚至如果覺得這樣也多,也可以使用兩行代碼實作初始調用:

1 2

var

scroll = 

new

ScrollLoad();     

scroll.Scroll(

function

(index) { scrollLoad(index); });

而在随後的使用過程當中又陸續發現了幾個問題。

第一個問題:this.Range = _range;  擷取内部_range值是不準确的,一旦使用SetRange更改_range值,而随後再使用this.Range擷取到的仍然是初始化的80。而不是更改後的值。這個解決的辦法是也采用匿名函數方式擷取:

1 2 3

this

.GetRange = 

function

() {

return

_range;

};

第二個問題:在家裡我維護的另一個網站微信開發使用滾動加載元件中使用微信的web開發工具測試過程當中,發覺竟然滾動到底後由于我的資料庫中有幾千條資料,居然一頁一頁不停加載,根本停不下來。我經過查找原因發現,是因為微信滾動加載時,滾動條一直在觸發滾動的距離底部80像素内。這個的解決辦法是每次加載一頁資料後重新設定滾動條距離底部的位置:

1

$(window).scrollTop($(window).scrollTop() - _range);

第三個問題:發現在第一次加載或重新整理時實際都是加載了兩頁。經過分析是因為在$(funciotn(){})中執行了一次scrollLoad(1);

而元件初始化過程中實際也會加載一頁造成。解決辦法就是$(funciotn(){})不再執行scrollLoad(1);

第四個問題:在點選搜尋按鈕時,本來按實際情況來說應該是自動加載搜尋後的第一頁,可是卻發現搜尋的頁碼在原來已經加載的頁碼情況下還會自動增加。這個問題的解決是增加了一個重設頁碼方法。而在搜尋方法中進行了引用:

1 2 3 4 5

function

Search(item,e)

{

scroll.ResetPageIndex();

scrollLoad(1);

}

經過對這三個問題的處理。最終我的滾動元件代碼修改為了:

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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80

//滾動加載閉包寫法

var

ScrollLoad = 

function

() {   

var

_range = 80;             

//距下邊界高度/機關px

var

_loaded = 

false

;            

//是否所有資料已加載完

var

_pageIndex = 1;      

var

_loading = 

false

;           

//是否正在加載

var

_totalheight = 0;

$(window).scroll(

function

() {

_Scroll(callback);

});

function

_Scroll(callback) {

//console.info("a1");

if

(_loaded) 

return

;    

//加載完畢,不在執行scroll函數

//console.info("scroll1");

var

srollPos = $(window).scrollTop();    

//滾動條距頂部距離(頁面超出視窗的高度)

_totalheight = parseFloat($(window).height()) + parseFloat(srollPos);

//console.info("a2");

if

(($(document).height() - _range) <= _totalheight ) {

if

(_loading) 

return

;

_loading = 

true

;

//加載資料過程中調整滾動條到視窗頂部的距離,防止滾動條一直在觸發加載區進而導緻不停的一頁一頁加載情況

$(window).scrollTop($(window).scrollTop() - _range);

//console.info("_range:" + _range);

//console.info("a2:"+$(window).scrollTop());

setTimeout(

function

() {

//console.info(typeof callback);

if

(

typeof

callback !== 

'function'

)

{

throw

new

TypeError(

'"callback" argument must be a Function'

);

}

//console.info(_pageIndex);

callback(_pageIndex);  

//此為外部函數調用

//_loading = false;

}, 1000);

}

//console.info("a3");

};

return

function

() {

//擷取距下邊界高度

this

.GetRange = 

function

() {

return

_range;

};     

//設定距下邊界長度/機關px

this

.SetRange = 

function

(value) {

_range = value;

//console.info("myrange:" + _range);

};      

this

.GetLoaded = 

function

() {

return

_loaded;

};

//設定滾動加載是否已經加載全部資料

this

.SetLoaded = 

function

(value) {

_loaded = value;

};

this

.GetPageIndex = 

function

() {

return

_pageIndex;

};      

this

.SetPageIndex = 

function

(value) {

_pageIndex = value;

};

this

.ResetPageIndex = 

function

() {

_pageIndex = 1;

};

//設定_loading為false

this

.SetLoading = 

function

() {

_loading = 

false

;

};

this

.Scroll = 

function

(callback) {

_Scroll(callback);

};

}

}();

​到這裡滾動加載元件開發也告一段落。滿足了我現在的需求,後續若再有問題,那再進行修改。需要說明此元件依賴于jquery。若要使用,需要添加jquery的引用。我開發使用的jquery版本是1.10

繼續閱讀