近來在學習移動設備的應用開發,接觸了jQuery mobile,在網上查閱相關資料時發現一個叫”iScroll“的小插件。其實這個iScroll插件跟jQuery mobile沒有多大關系,並不是基於jQuery mobile類庫開發的js插件,是一個獨立的js插件,使用它時不必預先引用jquery或jquery mobile類庫。關於iScroll的詳細介紹可以去它的官網了解或者去GitHub(https://github.com/cubiq/iscroll/)下載它的源碼學習。iScroll現在已經更新為iScroll-5,但並沒有加入太多新的功能,只是對前一個版本iScroll-4進行重構優化與修復,而且官方消息說不再對iScroll-4進行維護和技術支持,建議使用新版本。官方說法如下:
iScroll-5與iScroll-4的差別還挺大的,首先在聲明定義上寫法就不一樣了。
iScroll-4: | iScroll-5: |
var myScroll = new iScroll('wrapper', { useTransition: true, topOffset: pullDownOffset, onRefresh: function () { ... }, onScrollStart: function () { ... }, onScrollMove: function () { ... }, onScrollEnd: function () { ... } .... });
|
var myScroll = new IScroll('#wrapper', { mouseWheel: true, scrollbars: true, startY:-pullDownOffset ... }); //Event: scroll start myScroll.on("scrollStart", function() { ... }); //Event: scroll myScroll.on('scroll', function(){ ... }); //Event: scrollEnd myScroll.on("scrollEnd", function() { ... }); //Event: refresh myScroll.on("refresh", function() { ... }); ...
|
從上面兩者的示例代碼對比,以前的聲明定義是new iScroll('wrapper'...),現在變為new IScroll('#wrapper'...),類名變為大寫I開頭了,容器的Id要加上前綴#,某些參數名稱改變了(以前的topOffset變為startY和startX),關鍵是事件(event)不再是從options參數中指定,而是用on去注冊等等。
還有一個重大的不同是,iScroll-5按照功能點不同自身又划分為不同的版本,官方划分如下:
還有其他的差異就不再逐個列出了,大家可以到官網去了解。
其實iScroll之所以吸引我,主要是在iScroll-4中的demo里面有一個叫”pull-to-refresh“的示例,也就是我們說拉動更新,包括列表內容的下拉和上拉更新。這個功能在觸摸的移動應用上經常用到,可以不誇張的說是必不可少的。但這個重要的示例在新版iScroll-5的demo中卻找不到了。不知什么原因作者將它去掉了(據說iScroll-4的pull-to-refresh這個示例並不是作者的傑作),我在網上找了很久都沒發現有關於iScroll-5官方的pull-to-refresh示例,個別實現的例子倒也是有一些,但效果都不是很令人滿意。為了加深和鞏固對iScroll的學習,本人就參考iScroll-4的pull-to-refresh示例來實現iScroll-5的拉動刷新功能,同時也對iScroll-4的pull-to-refresh示例根據個人需求進行了一點改進。
首先給出iScroll-4的pull-to-refresh示例改動后的代碼:
1 <script type="text/javascript"> 2 3 var myScroll, 4 pullDownEl, pullDownOffset, 5 pullUpEl, pullUpOffset, _maxScrollY; 6 var generatedCount = 0; 7 8 function pullDownAction () { 9 setTimeout(function () { // <-- Simulate network congestion, remove setTimeout from production! 10 var el, li, i; 11 el = document.getElementById('thelist'); 12 13 for (i=0; i<3; i++) { 14 li = document.createElement('li'); 15 li.innerHTML = '(Pull down) Generated row ' + (++generatedCount);//Firefox does not suppose innerText, use innerHTML instead. 16 el.insertBefore(li, el.childNodes[0]); 17 } 18 19 myScroll.refresh(); // Remember to refresh when contents are loaded (ie: on ajax completion) 20 }, 1000); // <-- Simulate network congestion, remove setTimeout from production! 21 } 22 23 function pullUpAction () { 24 setTimeout(function () { // <-- Simulate network congestion, remove setTimeout from production! 25 var el, li, i; 26 el = document.getElementById('thelist'); 27 28 for (i=0; i<3; i++) { 29 li = document.createElement('li'); 30 li.innerHTML = '(Pull up) Generated row ' + (++generatedCount);//Firefox does not suppose innerText, use innerHTML instead. 31 el.appendChild(li, el.childNodes[0]); 32 } 33 34 myScroll.refresh(); // Remember to refresh when contents are loaded (ie: on ajax completion) 35 }, 1000); // <-- Simulate network congestion, remove setTimeout from production! 36 } 37 38 function loaded() { 39 pullDownEl = document.getElementById('pullDown'); 40 pullDownOffset = pullDownEl.offsetHeight; 41 pullUpEl = document.getElementById('pullUp'); 42 pullUpOffset = pullUpEl.offsetHeight; 43 44 45 myScroll = new iScroll('wrapper', { 46 useTransition: true, 47 topOffset: pullDownOffset, 48 onRefresh: function () { 49 console.log('maxScrollY-0:'+this.maxScrollY); 50 _maxScrollY = this.maxScrollY = this.maxScrollY + pullUpOffset; 51 console.log('maxScrollY-1:'+this.maxScrollY); 52 if (pullDownEl.className.match('loading')) { 53 pullDownEl.className = ''; 54 pullDownEl.querySelector('.pullDownLabel').innerHTML = 'Pull down to refresh...'; 55 } else if (pullUpEl.className.match('loading')) { 56 pullUpEl.className = ''; 57 pullUpEl.querySelector('.pullUpLabel').innerHTML = 'Pull up to load more...'; 58 this.scrollTo(0,this.maxScrollY,0); 59 } 60 }, 61 onScrollMove: function () { 62 console.log('maxScrollY-3:'+this.maxScrollY); 63 if (this.y > 5 && !pullDownEl.className.match('flip')) { 64 pullDownEl.className = 'flip'; 65 pullDownEl.querySelector('.pullDownLabel').innerHTML = 'Release to refresh...'; 66 this.minScrollY = 0; 67 } else if (this.y < 5 && pullDownEl.className.match('flip')) { 68 pullDownEl.className = ''; 69 pullDownEl.querySelector('.pullDownLabel').innerHTML = 'Pull down to refresh...'; 70 this.minScrollY = -pullDownOffset; 71 } else if (this.y <= (_maxScrollY - pullUpOffset) && !pullUpEl.className.match('flip')) { 72 pullUpEl.className = 'flip'; 73 pullUpEl.querySelector('.pullUpLabel').innerHTML = 'Release to refresh...'; 74 this.maxScrollY = this.maxScrollY - pullUpOffset; 75 } else if (this.y > (_maxScrollY - pullUpOffset) && pullUpEl.className.match('flip')) { 76 pullUpEl.className = ''; 77 pullUpEl.querySelector('.pullUpLabel').innerHTML = 'Pull up to load more...'; 78 this.maxScrollY = this.maxScrollY + pullUpOffset; 79 } 80 }, 81 onScrollEnd: function () { 82 if (pullDownEl.className.match('flip')) { 83 pullDownEl.className = 'loading'; 84 pullDownEl.querySelector('.pullDownLabel').innerHTML = 'Loading...'; 85 console.log('pull down---scroll end'); 86 pullDownAction(); // Execute custom function (ajax call?) 87 } else if (pullUpEl.className.match('flip')) { 88 pullUpEl.className = 'loading'; 89 pullUpEl.querySelector('.pullUpLabel').innerHTML = 'Loading...'; 90 console.log('pull up---scroll end'); 91 pullUpAction(); // Execute custom function (ajax call?) 92 } 93 } 94 }); 95 96 97 setTimeout(function () { document.getElementById('wrapper').style.left = '0'; }, 800); 98 } 99 100 document.addEventListener('touchmove', function (e) { e.preventDefault(); }, false); 101 102 document.addEventListener('DOMContentLoaded', function () { setTimeout(loaded, 200); }, false); 103 </script>
上面代碼高亮標注(黃色底色)的地方是主要的改動點,對於第15,30行標注的innerHTML原來例子是innerText,但我發現在firefox運行后新增的li會顯示不了內容(應該是firebox不支持innerText),改為innerHTML后就正常顯示了。其他處的改動主要是針對maxScrollY這個變量,這樣改主要是為了讓列表內容滾動到底部時上拉前不顯示提示欄。
iScroll-4 示例改動前: | iScroll-4 示例改動后: |
![]() |
![]() |
下面參照iScroll-4改動后的push-to-refresh示例來實現iScroll-5的拉動刷新功能。使用iScroll-5來實現的話,要引用的js類庫是iScroll-5細分后的iscroll-probe.js,按照前面的划分介紹,此細分版本主要是為了探查當前滾動位置(x,y)。在實現過程中我發現類庫有一小處源碼是需要改動的,主要是解決鼠標滑輪向頂部滾動時,不顯示下拉提示欄(這個問題在iScroll-4下是不存在的,應該跟iScroll-5去掉了minScrollY這個變量有關),我們希望在下拉時才會出現提示欄。
解決辦法其實也不復雜,只需改動一下iscroll-probe.js文件里面的第1122行處的一小段代碼。如下圖:
之所以這樣修改,主要是參考iScroll-4里面的源碼。如下圖:
好,源文件iscroll-probe.js的修改就完成了,下面給出iScroll-5拉動刷新功能的頁面js代碼:
1 <script type="text/javascript"> 2 3 var myScroll, 4 pullDownEl, pullDownOffset, 5 pullUpEl, pullUpOffset, _maxScrollY; 6 7 var generatedCount = 0; 8 9 function pullDownAction(){ 10 setTimeout(function () { // <-- Simulate network congestion, remove setTimeout from production! 11 var el, li, i; 12 el = document.querySelector('#scroller ul'); 13 14 for (i=0; i<3; i++) { 15 li = document.createElement('li'); 16 li.innerHTML = '(Pull down) Generated row ' + (++generatedCount);//Firefox does not suppose innerText, use innerHTML instead. 17 el.insertBefore(li, el.childNodes[0]); 18 } 19 if(myScroll){ 20 myScroll.refresh(); // Remember to refresh when contents are loaded (ie: on ajax completion) 21 } 22 }, 1000); // <-- Simulate network congestion, remove setTimeout from production! 23 } 24 25 function pullUpAction () { 26 setTimeout(function () { // <-- Simulate network congestion, remove setTimeout from production! 27 var el, li, i; 28 el = document.querySelector('#scroller ul'); 29 30 for (i=0; i<3; i++) { 31 li = document.createElement('li'); 32 li.innerHTML = '(Pull up) Generated row ' + (++generatedCount);//Firefox does not suppose innerText, use innerHTML instead. 33 el.appendChild(li, el.childNodes[0]); 34 } 35 if(myScroll){ 36 myScroll.refresh(); // Remember to refresh when contents are loaded (ie: on ajax completion) 37 } 38 }, 1000); // <-- Simulate network congestion, remove setTimeout from production! 39 } 40 41 function loaded() { 42 pullDownEl = document.querySelector('#pullDown'); 43 if (pullDownEl) { 44 pullDownOffset = pullDownEl.offsetHeight; 45 } else { 46 pullDownOffset = 0; 47 } 48 pullUpEl = document.querySelector('#pullUp'); 49 if (pullUpEl) { 50 pullUpOffset = pullUpEl.offsetHeight; 51 } else { 52 pullUpOffset = 0; 53 } 54 55 console.log('pullDownOffset:'+pullDownOffset); 56 console.log('pullUpOffset:'+pullUpOffset); 57 58 //Options of IScroll 59 var myOptions = { 60 mouseWheel: true, 61 scrollbars: true, 62 fadeScrollbars: true, 63 probeType:1, 64 startY:-pullDownOffset 65 }; 66 myScroll = new IScroll('#wrapper',myOptions); 67 console.log('maxScrollY-1:'+myScroll.maxScrollY); 68 _maxScrollY = myScroll.maxScrollY = myScroll.maxScrollY + pullUpOffset; 69 console.log('maxScrollY-2:'+myScroll.maxScrollY); 70 71 var isScrolling = false; 72 73 //Event: scrollStart 74 myScroll.on("scrollStart", function() { 75 if(this.y==this.startY){ 76 isScrolling = true; 77 } 78 console.log('start-y:'+this.y); 79 }); 80 81 //Event: scroll 82 myScroll.on('scroll', function(){ 83 if (this.y >= 5 && pullDownEl && !pullDownEl.className.match('flip')) { 84 pullDownEl.className = 'flip'; 85 pullDownEl.querySelector('.pullDownLabel').innerHTML = 'Release to refresh'; 86 //this.minScrollY = 0; 87 } else if (this.y < 5 && pullDownEl && pullDownEl.className.match('flip')) { 88 pullDownEl.className = ''; 89 pullDownEl.querySelector('.pullDownLabel').innerHTML = 'Pull down to refresh'; 90 //this.minScrollY = -pullDownOffset; 91 }else if (this.y <= (_maxScrollY - pullUpOffset) && pullUpEl && !pullUpEl.className.match('flip')) { 92 pullUpEl.className = 'flip'; 93 pullUpEl.querySelector('.pullUpLabel').innerHTML = 'Release to refresh'; 94 //this.maxScrollY = this.maxScrollY; 95 this.maxScrollY = this.maxScrollY - pullUpOffset; 96 } else if (this.y > (_maxScrollY - pullUpOffset) && pullUpEl && pullUpEl.className.match('flip')) { 97 pullUpEl.className = ''; 98 pullUpEl.querySelector('.pullUpLabel').innerHTML = 'Pull up to load more'; 99 //this.maxScrollY = pullUpOffset; 100 this.maxScrollY = this.maxScrollY + pullUpOffset; 101 } 102 103 console.log('y:'+this.y); 104 }); 105 106 //Event: scrollEnd 107 myScroll.on("scrollEnd", function() { 108 console.log('scroll end'); 109 console.log('directionY:'+this.directionY); 110 console.log('y1:'+this.y); 111 console.log('maxScrollY-3:'+this.maxScrollY); 112 if (pullDownEl && !pullDownEl.className.match('flip') && this.y > this.options.startY) { 113 console.log('resume'); 114 this.scrollTo(0, this.options.startY,800); 115 } 116 else if (pullDownEl && pullDownEl.className.match('flip')){ 117 pullDownEl.className = 'loading'; 118 pullDownEl.querySelector('.pullDownLabel').innerHTML = 'Loading...'; 119 // Execute custom function (ajax call?) 120 if (isScrolling) { 121 console.log('before pull down action:'+this.y); 122 pullDownAction(); 123 console.log('after pull down action:'+this.y); 124 } 125 } 126 else if (pullUpEl && pullUpEl.className.match('flip')) { 127 console.log('pull up loading'); 128 pullUpEl.className = 'loading'; 129 pullUpEl.querySelector('.pullUpLabel').innerHTML = 'Loading...'; 130 // Execute custom function (ajax call?) 131 if (isScrolling) { 132 console.log('before pull up action:'+this.y); 133 pullUpAction(); 134 console.log('after pull up action:'+this.y); 135 } 136 } 137 isScrolling = false; 138 }); 139 140 //Event: refresh 141 myScroll.on("refresh", function() { 142 143 console.log('maxScrollY-4:'+this.maxScrollY); 144 _maxScrollY = this.maxScrollY = this.maxScrollY+pullUpOffset; 145 console.log('maxScrollY-5:'+this.maxScrollY); 146 147 if (pullDownEl && pullDownEl.className.match('loading')) { 148 pullDownEl.className = ''; 149 pullDownEl.querySelector('.pullDownLabel').innerHTML = 'Pull down to refresh'; 150 this.scrollTo(0,this.options.startY,0); 151 } else if (pullUpEl && pullUpEl.className.match('loading')) { 152 pullUpEl.className = ''; 153 pullUpEl.querySelector('.pullUpLabel').innerHTML = 'Pull up to load more'; 154 this.scrollTo(0,this.maxScrollY,0); 155 } 156 157 console.log('refresh finished!'); 158 }); 159 160 setTimeout(function () { document.getElementById('wrapper').style.left = '0'; }, 500); 161 162 } 163 164 document.addEventListener('touchmove', function (e) { e.preventDefault(); }, false); 165 166 </script>
運行效果:
iScroll-4: | |||
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
iScroll-5: | |||
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
后話:按照官網的介紹iScroll-5比iScroll-4更快,性能更好。但在上面的拉動刷新的示例中,iScroll-5在firefox中運行時比iScroll-4要占用更多的CPU,感覺iScroll-4要流暢得多,但僅限於拉動功能的比較,至於iScroll的其他功能沒有測試對比過,所以也不敢以偏概全地斷言說iScroll-5的性能就比上iScroll-4。有興趣的朋友可以去測試一下,測完后給我分享一下結果,謝謝!