在做移动端的需求有时候需要用到对滚动结束进行事件绑定以完成一定的功能,先来了解一下现状。

 

在移动端,正常的一次swipe动作会依照以下顺序触发事件:

touchstart -> touchmove -> touchend ->scroll

一、IPAD

通过以下代码进行测试:

var timestart =0;
        var timer = null;
        function log(){
            console.log("---------settimeout" + (new Date()-timestart) );
        }
        $(window).on('scroll',function(){
            console.log('-----scrollend:' + (new Date() - timestart) +'ms');
            timestart = new Date();
        });
        $(window).on('touchstart',function(){
            console.log('touchstart');
        });
        $(window).on('touchmove',function(){
            console.log('touchmove');
        });
        $(window).on('touchend',function(){
            timestart = new Date();
            console.log('touchend');
            timer =  setTimeout(log,1);
        })

当“tap”时,依次产生如下事件:

touchstart -> touchend

当"swipe"时,依次产生如下事件:

touchstart -> touchmove * 多次 -> touchend -> scroll

在ipad中,touchend事件在手指离开时触发,由于是swipe动作,所以在手指离开后,屏幕还会滚动一段时间,直至最后停止,在最后的停止事件会触发scroll事件。

可见,在ipad中,滚动结束的判断只需要监听scroll事件即可。

那么从touchend到scroll这段时间如何操作dom呢?使用以下测试代码,一次swipe从touchend到scroll的时间大概经过0-2000ms不等(也许可能更长,依据加速度)。现在向touchend中添加setTimeout事件,设定延迟时间为1ms(几乎一定在scroll之前)。实验了四次,得到结果如下(使用weinre远程调试)

collectionView滚动结束时是第几个cell iOS 滚动结束事件_滚动

可以从结果中看到,在ipad中,settimeout并没有如预想的在scrollend之前触发,反而在scrollend触发后才触发。

由这种情况可以推测,事件队列中的顺序为 touchend回调 -> scroll回调 -> settimeout函数。

所以,如果想操作touchend到scroll这段时间的动作,在ipad下是不可实现的

 

二、Android

依旧使用上述测试代码。

当“tap”时,依次产生如下事件:

touchstart -> touchend

当"swipe"时,依次产生如下事件:

touchstart -> touchmove  -> scroll*多次

另外需要注意的是,在android下,swipe虽然不会触发touchend事件,但是会在scroll事件之前触发一次touchcancel事件,即:

touchstart -> touchmove -> touchcancel->scroll*多次

在swipe过程中,并没有触发touchend事件,同时却触发了多次scroll事件,并在第一次触发scroll事件时有大概1000ms的延时。即使手指在屏幕上来回滑动,也只触发一次touchmove事件,后续过程均为scroll事件。

 

所以,如果需要做兼容性良好的滚动结束的判断,是不能根据touchend事件的触发做判断的,同时,由于scroll事件在Android中会触发多次,而在苹果中只触发一次(最后停止时),所以在对scroll进行事件绑定判断时,需要通过setTimeout的方法不断判断当前scrollTop与旧的scrollTop的值是否一致,如果不一致则说明屏幕仍在滚动。实现代码如下:

var count = 0, timer = null; 
        var oldTop = newTop = $(window).scrollTop(); //为了方便起见,使用jquery或者zepto框架
        function log(){
            if(timer) clearTimeout(timer);
            newTop = $(window).scrollTop();
            console.log(++count,oldTop,newTop);
            if(newTop === oldTop) {
                clearTimeout(timer);
                //已停止,写入业务代码
            } else{
                oldTop = newTop;
                timer = setTimeout(log,100);
            }        
        }
        $(window).on('scroll',log);

然而忧桑的是,据说很多android的机型在触发scroll事件的时候,仍旧不能遵守规则按照正确的方式触发,所以如果我们监听scroll事件,仍旧有几率出bug,so结论是~~~~

 

三、结论

针对ipad和Android的滚动结束监听,其兼容性处理方案为:监听touchmove事件

来保证最好的兼容。即代码如下:

var count = 0, timer = null; 
        var oldTop = newTop = $(window).scrollTop(); //为了方便起见,使用jquery或者zepto框架
        function log(){
            if(timer) clearTimeout(timer);
            newTop = $(window).scrollTop();
            console.log(++count,oldTop,newTop);
            if(newTop === oldTop) {
                clearTimeout(timer);
                //已停止,写入业务代码
            } else{
                oldTop = newTop;
                timer = setTimeout(log,100);
            }        
        }
        $(window).on('touchmove',log);

这种方式由于过早的监听可能会损耗更多的性能,所以如果你只做ipad的话就监听scroll(或touchend);只做android就监听touchcancel。