从移动应用开发转到webapp开发的研究也就一年多时间,深深感受到了浏览器内核的差异化给开发者带来的黑洞般的恶意。但是无论如何,现在技术已经做为一种服务,开发者自然也需要不断探索各种可能。希望以此博做为猴年的开端吧,也算是给来年的鼓励。
言归正传,IScroll的出现,使得移动端webapp的滚动效果更加接近原生,而且应用场景非常广泛,除了最基本的页面滚动,还可以支持分页滚动、通过内部触摸事件监听实现的上拉下拉刷新等,同时可以与很多框架一起使用,不存在冲突问题。
而Agile Lite在设计之初就是要实现一个接近原生效果的H5开源框架。所以也将IScroll引进来,目前使用的是IScroll5版本。这里主要是跟大家分享一下在Agile Lite中使用IScroll5的心得和遇到的坑如何解决。
IScroll5的使用场景非常丰富,但是在Agile Lite中并没有完全使用,而是通过组件的方式将IScroll5集成进来,主要用到的组件为:
scroll组件:实现页面内容的纵向和横向滚动
refresh组件:实现页面上拉下拉的刷新组件效果
slider组件:实现页面中类似于banner的滑动块展示
可以通过此链接了解这些组件(http://www.agilelite.net/extend.html#mycomponent)
当然,由于对IScroll5的封装,很多开发者在开发自己的组件的时候也可以使用,比如日期时间控件的上下滑动选择日期,日历控件的左右切换换月份等等,开发者可以在Agile Lite中看到对IScroll5的应用。
IScroll5有很多细分版本,各自使用场景不同。而Agile Lite做为一个框架来设计,需要对组件的扩展和监听等达到二次开发的级别,让开发者可以更灵活的使用。所以这里选择了iscroll-probe.js这个细分版本,官方对此版本的解释为:probing the current scroll position is a demanding task, that's why I decided to build a dedicated version for it. If you need to know the scrolling position at any given time, this is the iScroll for you.
翻译过来大意是:探索当前的滚动位置是一项艰巨的任务,这就是为什么我决定为它建立一个专门的版本。如果你想知道任何时候的滚动位置,那你可以使用它。
那刷新组件需要有顶部和底部的动画效果自然是需要实时监听滚动位置的,而开发者在开发过程中也很可能需要监听滑动到顶部和底部,以及滑动到任意位置的需求,为了能满足这些需求,我们也坚定的选择了iscroll-probe.js这个细分版本,并进行了适当的封装(并不是修改源码)。
好吧,确定了使用IScroll5,那无论遇到什么坑也要坚强的挺过。
下面就来一一击破!
由于IScroll对象的使用是需要在JS中new一个IScroll类来实现的,而Agile Lite框架做为一个整体的前端框架,是希望内部的所有元素都是组件化的,包括滚动区域在内。所以需要对IScroll对象进行封装,在Agile Lite启动的时候通过组件化的设计来实例化每个IScroll对象。
所以,Agile Lite采用了组件+控制器的模式,直接在元素标签通过自定义属性来定义组件, Agile Lite遍历所有的组件,并实例化对应的组件。比如:data-scroll=”verticle”指明当前元素会当成一个垂直滚动的组件,Agile Lite内部会针对此元素实例化一个IScroll对象。
当然,只是简单的实例化还不行,开发者可能还需要动态操作IScroll对象,最常见的就是当scroll区域大小改变要刷新IScroll对象。那Agile Lite在实例化IScroll组件后会触发一个名为scrollInit自定义事件,开发者可以针对scroll组件元素来监听此事件完成后续对IScroll对象的操作。比如:
//当scroll初始化会进入此监听 $('#main_article').on('scrollInit', function(){ var scroll = A.Scroll(this);//可以得到滚动对象 //监听滚动到顶部事件,可以做一些逻辑操作 scroll.on('scrollTop', function(){ A.showToast('滚动到顶部'); //scroll.refresh(); //如果scroll区域dom有改变,需要刷新一下此区域 }); //监听滚动到底部事件,可以做一些逻辑操作 scroll.on('scrollBottom', function(){ A.showToast('滚动到底部'); //scroll.refresh(); //如果scroll区域dom有改变,需要刷新一下此区域, }); });
通过这样的设计,Agile Lite也可以很方便的自定义IScroll的事件,开发者通过scroll.on方法可以方便的监听这些已经封装好的自定义事件,比如滑动到顶部和底部的事件。
点击事件的问题总的归纳主要有两个:onclick事件不生效;onclick事件生效但是会触发两次。
这估计是使用IScroll5的开发者遇到的最常见的问题,解决办法有很多,但大多是修改源码来处理的,为了不打破IScroll5的完整性和减少后续升级维护的压力,所以暂时没有考虑修改源码。
经过对源码分析,IScroll5对tap事件是可以支持的很好的,所以Agile Lite里建议点击事件统一使用tap来实现。但是tap在PC端浏览器里是没有的,那Agile Lite封装了一个变量A.options.clickEvent来定义点击事件名,当在PC上位click,而在移动端浏览器上位tap,开发者只需要监听事件改成此变量即可一套代码在不同端使用。比如:
$('a.button').on(A.options.clickEvent, function(){ /* your code*/ //可以通过给A添加自定义属性,在事件中获取,比如var id = $(this).data('id');//需要给A添加data-id="XXX"自定义属性 return false; });
另外,由于tap事件是自定义事件而非原生JS事件,所以使用此事件必须通过时间监听方式,虽然现在界面与数据分离模式的各类框架都已经使用事件监听,但是可能在一些特殊场合或者开发者习惯上还会有可能使用在元素标签上使用事件属性,所以Agile Lite也给元素添加了自定义的点击属性data-click,此属性跟onclick属性用法完全一样,只是内部实现是在tap事件触发的时候调用。比如,<a data-click="handle()">请点击</a>,当点击此元素时就会触发handler()这个方法。
IScroll5在不同机型,特别是Android系统的机型上的性能和效率是差别很大的。所以源码内部对Android还区分了isBadAndroid,直白说就是是否为不友好的android系统。
经过对此属性的多次尝试,发现内部默认判断的isBadAndroid部分机型闪屏很严重,最后查看源码发现isBadAndroid为true的时候,对滚动指针的动画太快,造成视差上的闪屏。
最后,Agile Lite内部使用的scroll组件全局设置为
IScroll.utils.isBadAndroid = false;//处理页面抖动
以处理页面抖动。
多个IScroll对象对应的元素如果是相互嵌套的,则操作内部的IScroll对象,外部的也会被触发,所以为了能够使嵌套中的IScroll组件不相互干扰,Agile Lite使用了IScroll5的this.enabled属性,此属性为true则IScroll正常工作,如果为false则停止工作。
具体方法为自定义两个事件:__setdiabled和__setenabled,分别设置enabled属性的false和true,比如:
$scroll.on('__setdiabled', function(){ this.enabled = false; }); $scroll.on('__setenabled', function(){ this.enabled = true; });
在需要的时候触发相应的事件来启用和停止IScroll的工作状态。比如不同slider之间的嵌套,则上一层slider停止工作:
myScroll.on('beforeScrollStart', function(){ var $outerSlider = $el.parent().closest('[data-role="slider"]'); if($outerSlider.length==0) return; outerSlider = A.Slider($outerSlider[0]); outerSlider._execEvent('__setdiabled'); });
我们都知道,当IScroll对象中的DOM接口发生变化,通常都是需要刷新IScroll对象的,但是如果能够自动刷新就可以省掉很多操作,代码可读性和维护性也大很多。
而经过分析,一般带来DOM变化无非两种:
一种是图片的加载:H5中的图片都是异步加载的,当图片没加载完成IScroll对象可能已经实例化完成,当加载完成图片撑大了scroll区域,导致内容看不全。
第二种就是动态往scroll区域增删改DOM元素,最常见的就是innerHTML,而每一次的增删改DOM也是需要刷新IScroll对象的。
所以,Agile Lite封装了懒人加载组件和数据注入的API方法分别应对这两种情况。
懒人加载组件通过监听img的load事件,当图片加载完成自动刷新img所在的IScroll对象;而数据注入方法,不仅仅是提供了数据与界面模板的渲染,而且在将渲染的结果直接注入到某个IScroll区域内,并自动刷新IScroll对象,完成从数据请求(JSON格式),数据渲染(artemplate)到数据注入和IScroll自动刷新的全过程,开发者只需要调用一句话即可完成这么复杂的工作。比如:
在Agile Lite 的article中有一个id为template的模板:
<article data-role="article" id="main_article" data-scroll="pull" class="active" style="top:44px;bottom:0px;"> <div class="scroller"> <ul id="content" class="listitem"> <li class="sliver">介绍</li> <li> Agile Template是基于artTemplate的模板注入封装 </li> <li class="sliver">基本用法</li> <li> A.template('#templateId').renderReplace|renderBefore|renderAfter(url|data,callback); </li> <li class="sliver">下拉或上拉刷新查看效果</li> <script id="template" type="text/html"> <% for (i = 0; i < list.length; i ++) { %> <li>条目内容 <%= i + 1 %> :<%= list[i] %></li> <% } %> </script> </ul> </div> </article>
往这个模板使用数据注入如下:
A.template('#template').renderBefore({list:['Agile Lite移动前端框架', 'ExMobi三大引擎完美融合']},function(h){//h为渲染后的dom对象 //refresh.refresh(); });
renderBefore是指数据注入在模板所在的dom结构之后,渲染之后Agile Lite就会自动刷新IScroll区域。
可以通过此链接体验http://demo.exmobi.cn/process/service/agile_lite/www/agiletemplate.html
需要特别提出的还有懒人加载组件在slider组件内的使用技巧,我们知道slider组件通常是做banner展示的,但是banner通常是图片,宽度一般都是100%,高度则是自适应,这时候slider的高度应该设置为多少是不可知的,因为由于使用了自适应,高度取决于宽度,而宽度是100%。这时候用IScroll来实现slider也会经常出现告诉算不准的情况,使用懒人加载可以轻松的解决这些问题。
Agile Lite封装的slider组件主要是完成类似于banner的广告区域的展示,本质上是使用IScroll对于page页切割的实现(也就是snap的使用)。但是在实际使用过程发现,banner的个数通常是变化的,有的时候广告多,有的时候广告少,如果已经实例化过IScroll对象,内部再添加新的元素,这时候IScroll内对于page页的计算就会出错。
所以,Agile Lite在slider组件对应的IScroll对象中监听了refresh事件,只要refresh就会重新计算scroll区域内的page数。
比如:
myScroll.on('refresh', function(){ init(); createDots(); });
具体代码大家可以去看Agile Lite的源码,这里不再赘述。
OK,这次就先跟大家聊这么多。