iScroll学习小结

前言

最近项目需要实现一个fixed标题栏的功能,很普通的功能,实现核心也是在sroll事件中切换到fixed状态即可,但是在某些版本ios的某些内核中,在惯性滚动过程中不执行任何js代码,亦即不会触发scroll事件,基本任何事情都做不了,为了解决这个问题不得不使用div内滚动,然后使用iscroll库实现滚动逻辑。

基于使用过程中的一些问题,抱着学习的态度,稍微看了一下源代码,现把学习所得记录如下。

源代码学习

核心实现

滑动相关组件(如swipe库)的实现基本都是类似的,就是通过3个核心事件:touchstart,touchmove,touchend完成操作。

switch ( e.type ) {
    case 'touchstart':
    case 'mousedown':
        this._start(e);
        break;
    case 'touchmove':
    case 'mousemove':
        this._move(e);
        break;
    case 'touchend':
    case 'mouseup':
        this._end(e);
        break;
}

注:下面的源码只罗列核心部分,而且只展示y轴方向

touchstart需要做的事情有:

function _start(e) {
    var point        = e.touches ? e.touches[0] : e;

    //[1]
    //初始化相关数据,一般是开始滑动的位置基点,时间基点
    //还有相关的变量
    this.moved        = false;
    this.distY        = 0;
    this.directionY = 0;
    this.startTime  = utils.getTime();
    this.startY     = this.y;
    this.pointY     = point.pageY;

    //[2]
    //如果正在滑动中,需要对此做处理,一般策略有:
    //1. 在当前滑动状态的基础上,叠加新的滑动状态
    //2. 立刻停止当前的滑动,开始新的滑动
    //iscroll使用的是方案2
    //方案1对于状态处理,滑速计算等方面略偏复杂,但这是更加合理的处理策略(原生的scroll也是这样的)
    //这有点类似开车时踩油门的场景,想象一下就清楚了。。。
    if ( !this.options.useTransition && this.isAnimating ) {
        this.isAnimating = false;
        this._execEvent('scrollEnd');
    }
}

touchmove需要做的事情有:

function _move(e) {
    //[1]
    //计算位置和时间,各种增量
    var point        = e.touches ? e.touches[0] : e,
        deltaY        = point.pageY - this.pointY,
        timestamp    = utils.getTime(),
        newY, absDistY;

    this.pointY        = point.pageY;
    this.distY        += deltaY;
    absDistY        = Math.abs(this.distY);

    //[2]
    //判定是否是标准滑动,防止手抖干扰
    //干扰有时候是很大的,特别是有惯性滑动逻辑的时候就更甚了,所以这个细节是少不了的
    if ( timestamp - this.endTime > 300 && (absDistX < 10 && absDistY < 10) ) {
        return;
    }

    newY = this.y + deltaY;

    //[3]
    //判断滑动是否超出范围了
    //自从ios出现了负向滚动效果之后,各种滑动组件都跟着实现了这种bounce效果
    if ( newY > 0 || newY < this.maxScrollY ) {
        newY = this.options.bounce ? this.y + deltaY / 3 : newY > 0 ? 0 : this.maxScrollY;
    }

    //[4]
    //触发scrollStart事件
    //一个健全的组件肯定有相关的插口,一般都是用事件机制实现的
    //这里的细节是,开始事件是要在判定为标准滑动才会触发的,并且只触发一次
    //如果考虑不细的话,很容易会在touchstart事件中触发事件
    if ( !this.moved ) {
        this._execEvent('scrollStart');
    }

    this.moved = true;

    //[5]
    //万事俱备,让页面(元素)滑过去吧!
    this._translate(newX, newY);
}

touchend需要做的事情有:

function _end(e) {
    //[1]
    //进行必要的计算
    var duration = utils.getTime() - this.startTime,
        newY = Math.round(this.y),
        distanceY = Math.abs(newY - this.startY);

    this.endTime = utils.getTime();

    //[2]
    //最后的位置也要滑过去
    this.scrollTo(newX, newY);    // ensures that the last position is rounded

    //[3]
    //实现惯性滑动
    if ( this.options.momentum && duration < 300 ) {
        momentumY = this.hasVerticalScroll ? utils.momentum(this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0, this.options.deceleration) : { destination: newY, duration: 0 };
        newY = momentumY.destination;
        time = Math.max(momentumX.duration, momentumY.duration);
        this.isInTransition = 1;
    }

    if ( newX != this.x || newY != this.y ) {
        this.scrollTo(newX, newY, time, easing);    
        return;
    }

    //[4]
    //触发滑动结束事件
    this._execEvent('scrollEnd');
}

基本所有滑动相关的组件所做的事情都是这些,都可以借鉴一二的。

特殊css prefix缓存

用js处理特殊css的时候,可以先缓存prefix,这样就不用每次都操作所有的内置属性

var _elementStyle = document.createElement('div').style;
var _vendor = (function () {
    var vendors = ['t', 'webkitT', 'MozT', 'msT', 'OT'],
        transform,
        i = 0,
        l = vendors.length;

    for ( ; i < l; i++ ) {
        transform = vendors[i] + 'ransform';
        if ( transform in _elementStyle ) return vendors[i].substr(0, vendors[i].length-1);
    }

    return false;
})();

function _prefixStyle (style) {
    if ( _vendor === false ) return false;
    if ( _vendor === '' ) return style;
    return _vendor + style.charAt(0).toUpperCase() + style.substr(1);
}

事件绑定

addEventListener绑定事件可以传入一个对象而不是一个cb函数,事件触发的时候,就会调用该对象的handleEvent方法来处理事件。例如:

var event = {
    handleEvent: function(e) {
        switch ( e.type ) {
            case 'touchstart':
                this._start(e);
                break;
            case 'touchmove':
                this._move(e);
                break;
            case 'touchend':
                this._end(e);
                break;
        }
    },
    _start: function() {},
    _move: function() {},
    _end: function() {}
}
el.addEventListener('touchstart', event);
el.addEventListener('touchmove', event);
el.addEventListener('touchend', event);

这种绑定方式的优点有:

  1. 删除事件方便
  2. 事件集中处理
  3. 程序结构清晰

还记得那种绑定事件时bind(this)的日子吗。。。 这种方式也方便了实现事件代理

事件触发频率调整

对于一些触发频率较高的事件,我们通常会控制一下事件处理的频率,例如scroll,resize事件。 另一方面,在实现一个公共组件的时候可以考虑从组件本身来解决这个问题,iScroll通过配置来设置scroll事件的触发频率

//下面代码在_move方法里
//probeType == 1 则300ms才会触发一次scroll
if ( timestamp - this.startTime > 300 ) {
    this.startTime = timestamp;

    if ( this.options.probeType == 1 ) {
        this._execEvent('scroll');
    }
}

//probeType > 1 则一直触发
if ( this.options.probeType > 1 ) {
    this._execEvent('scroll');
}

缺点与使用问题

下面是针对版本5.1.3的iscroll使用过程中的一些问题

1. 没有插件版

iScroll没有zepto/jquery插件版本,一些基础方法都需要自己实现,导致了库的体积偏大。

2. 没有暴露停止滑动(惯性滑动)的接口

通过查看源代码找到了停止滑动的方法,如下:

var iScroll = new IScroll({ /* ... */ });
//直接通过修改iScroll对象的状态来停止滑动
//通过这种方式停止动画是不会触发scrollEnd事件的!
iScroll.isAnimating = false

3. 调用scrollTo方法不会触发scroll事件

可以通过scrollTo方法来手动滑动,但是这样的滑动过程是不会触发scroll事件的。

总结

在使用iScroll的过程中遇到不少坑,但使用起来还是比较容易的,文档也比较齐全。 iScroll在实现上也非常成熟,里面许多实现细节都是值得学习的

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏移动开发面面观

Android dependencies 基础知识

在build:gradle2.x的时代,我们在进行第三方依赖时,会有一些尴尬的问题。比如,我们制作了一个库,依赖了Glide2.0 。项目组集成我们的库,同时也...

502
来自专栏QQ音乐技术团队的专栏

浅谈 Android 自定义锁屏页的发车姿势

一、为什么需要自定义锁屏页   锁屏作为一种黑白屏时代就存在的手机功能,至今仍发挥着巨大作用,特别是触屏时代的到来,锁屏的功用被发挥到了极致。多少人曾经在无聊的...

3249
来自专栏HTML5学堂

移动端框架 滚动类 iScroll5

HTML5学堂:移动端开发中,经常遇到需要模拟APP的效果header或是footer固定住,里面的内容区域实现滚动。但是对低端手机单纯使用CSS是兼容不了,需...

3599
来自专栏玄魂工作室

[翻译]整合鼠标、触摸 和触控笔事件的

Html5 Pointer Event Api 原文链接 https://mobiforge.com/design-development/html5-poin...

3186
来自专栏哲学驱动设计

私活后的 WPF 设计经验总结

WPF 是一个界面层框架技术,要对 WPF 技术达到熟练运用的程度,需要同时拥有开发和设计两方面的知识。而我作为一名开发人员,以前的总结都是站在开发人员的角度,...

1917
来自专栏谦谦君子修罗刀

React Native备课笔记Day01一、React Native介绍二、特点分析三、推荐网站以及运行第一个react native项目四、环境搭建五、React Native文件结构六、View

(本节包括React Native介绍、特点分析、环境搭建、RN文件结构、View组件讲解、FlexBox布局及props与state) 一、React Nat...

30811
来自专栏lgp20151222

Java引用外部字体(路径引用)的一些坑

然后,一路踩了jvm关于字体的坑,重点是,java的报错很随意,甚至不报错,建议直接看最后面.

891
来自专栏walterlv - 吕毅的博客

CaptureMouse/CaptureStylus 可能会失败

2017-10-09 11:05

611
来自专栏HTML5学堂

HTML5视音频代码实例 & WEBM格式转换器

HTML5视音频代码实例&WEBM格式转换器 HTML5学堂:WebM由Google提出,是一个开放、免费的媒体文件格式。WebM 影片格式其实是以 Matro...

3768
来自专栏我就是马云飞

这才是真正的万能圆角ImageView

不知道有没有人记得我去年写过一个圆角的imageview。不知道的可以先去看看:万能圆角imagview,本文是基于上一篇的内容进行添加以及修改的。不然直接看这...

2109

扫码关注云+社区