最近在做hybrid APP,其中的下拉刷新与滚动加载把我折腾的够呛,不是说效果有多难实现,而是各种手机都要适配。安卓机坑爹,安卓机坑爹,安卓机坑爹(重要的事情说三遍)。个人感觉,安卓机浏览器比之前的IE还要坑爹,所以我们有理由相信,未来的某一天,安卓机的各式坑爹的浏览器,终究会被我们抛弃,就如现在的IE一样,我不觉能够适配各种安卓机是一种非常牛逼的本领,毕竟它们是一种BUG,是一个异类。理想总是美好的,现实是我还要吃饭,只能顺着老板的意思,去做兼容。
坑1:列表页数据太多,下拉的时候动画效果出不来
最初遇到这个问题,我怀疑是我的DOM结构有问题,各种改DOM结构都无法解决,而安卓机却没有问题。这时,我对安卓机充满了好感(可能是唯一的好感吧)。我跟IOS开发的同事交流,得知原生的IOS列表页性能非常好,列表页看起来是很多数据滚动,而实际上只有部分数据在列表当中,当滚动到顶部的时候,数据会往顶部加载,此时底部的数据会被从列表中清除,往下滚动同理。这么做是为了让IOS APP的滚动时性能更好。这时我想:IOS浏览器会不会也是有同样的需求,当页面数据过多的时候,也需要做同样的加载呢?此时我先验证我的想法是正确的,我把原先的七八十条数据的列表,缩减到只有10几条,就这样下拉的时候动画出来了。
问题原因找到了,那么解决办法呢?首先我想试试模拟同事说的原生IOS加载列表的算法,感觉实现起来有点难度,于是就另辟蹊径—-开启3D加速。
方法: 给列表页中的每一项的最外层添加样式 -webkit-transform:translateZ(0);
原因: webkit在绘制页面时会将结构分为各种层,当层足够大时就会变成很大的平铺层。这样一来webkit在每次页面结构发生变化时不需要都渲染整个页面而是渲染对应层了,这对渲染速度来说相当的重要。webkit会给各种层分配一定大小的“备份存储区”在内存里缓存起来,这就是绘制层的上下文,通过这个上下文就可以很容易的实现各种效果(动画,3D变换等),“备份存储区”内存占用大小不仅依层而定,跟设备和显示方式也是有关的,假设这在普通屏幕下是1:1的,但在Retina屏幕下则是1:2的,并且放大时这个量会成倍增加;一张图片是10X10,普通屏幕分配的就是10X10,Retina初始则是20X20。这也表明Retina是更加消耗内存的。当层很大时,意味着“备份存储区”会消耗更大的内存,为了避免这点,webkit并不会绘制一个很大的层来存储一个很大的页面,比如说平铺层则会拆分成很多的块来绘制,即尽占用尽可能小的内存,只是将可视范围内的那部分渲染出来。这就是为什么我们在大页面滚动时会发现下面的内容慢慢显示,向上滚动时上面的内容还慢慢显示的原因。添加 -webkit-transform:translateZ(0);
可以将滚动区域可视范围的列表项元素缓存起来,记webkit强制缓存起来即是将他们独立成一个层,而且这个层当然不会很大否则会被视为平铺层处理了。一般列表里项里的元素不会像页面主容器一样的大的。
坑2:页面向下滚动的时候,fixed固定的元素会错位,甚至抖动
这个问题严重影响了web的展示效果,包括安卓和IOS都会有这个问题,解决办法同坑1,3D加速设置之后这个问题也解决了
坑3:适配小米手机
问题出现在touchmove事件,touchmove事件只触发一次不能连续触发,除了touchmove,touchend更加惨,压根没有触发。
其他部分安卓机也可能会有类似的问题(我还没测试),在小米的论坛上,早就有人贴出了问题帖子,“危害武林”的小米却没有解决。不过这个兄弟的解决办法,更像是做广告的,他在给一个付费的移动端框架打广告。我直接PASS了他的方法。我查找了相关资料,终于找到了一个貌似正解的描述:
1 | On Android ICS if no preventDefault is called on touchstart or the first touchmove, |
我给touchstart,touchmove,touchend都加上了e.preventDefault();
小米可以全部监听到这三个事件了。不过这带来了更为严重的问题— 列表页中的a标签全部失效无法点击。e.preventDefault();
阻止了浏览器的事件的默认效果,所以也就阻止了a标签的跳转,看来这个方法还不能乱加。后来突然脑袋灵光一闪,既然在touchstart
中加了event.preventDefault
会导致不触发click事件,那我在touchmove中加可以吧?后来经过测试,只能够在touchmove中添加,start和end都不能添加。touchmove中有event.preventDefault()方法,还需要加上方向判断,比如我做的下拉刷新功能,所以我就添加了逻辑:如果手指是往下拉的时候,就在touchmove里添加e.preventDefault();
此时如果不加这个方法,小米手机的浏览器touchmove事件只会触发一次,不能连续触发,所以就不能够出现下拉刷新的效果了,添加该方法就可以连续触发touchmove事件了。
出现该问题的原因:该问题多出现在 Android 4.0 和 4.4 版本里,在touchmove触发的瞬间就触发了touchcancel,然后就没有然后了,所以要在touchmove触发的时候禁止它的默认行为
坑4:小米手机最后的touchend事件有时候不触发
页面滚动的时候,正常的事件触发顺序为touchstart -> touchmove -> touchend ->scroll
,我的小米测试机有时候不触发touchend事件,而我在touchend中添加了滚动条回复位置的方法,方法没执行会造成页面中滚动条错位。
小米手机测试图:
别人的解决方法:
1 | listenTouchEndForAndroid(){ |
他的方法是每隔100ms检测两次的滚动条位置是否相同,如果相同,那么就是滚动结束了,我测试了一下,可以模拟touchend事件,而且我的小米手机,滚动的时候,看上去没有卡顿,但是我总感觉这种方法,太牵强了。
我的解决方法:
1 | /** |
在滚动事件监听的方法里,判断如果是安卓机并且滚动到页面的头部,给滚动区域强制恢复位置,测试完了发现效果还不错,实现了我的需求。
坑5:安卓机下拉的时候,会把顶部的状态栏的背景色部分拉到web区域
测试了部分安卓机,当scroll到顶部的时候,会把机器最顶部的状态栏的背景色部分拉到web区域,会有很奇怪的效果,如果加了e.preventDefault(),将不会触发该效果。
部分代码
直接截取我的项目部分中下拉刷新与滚动加载的代码,希望对你有所启发(react框架):
1 | constructor(props, context) { |