iScroll-5拉动刷新功能实现与iScroll-4上拉刷新的一点改进
近来在学习移动设备的应用开发,接触了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。有兴趣的朋友可以去测试一下,测完后给我分享一下结果,谢谢!