iscroll源码实现原理(二)

iscroll的核心代码core.js

iscroll的核心代码在core.js中,包括他的构造函数,原型方法等.而这其中最重要的属于3个方法

  • _start在滚动开始时候的触发
  • _move在滚动过程中的处理
  • _end在滚动结束后的处理

iscroll构造函数

用于初始化一些属性

具体的属性可以去查阅文档

下面来看iscroll的原型方法

_init方法

_initEvents: function (remove) {  
        var eventType = remove ? utils.removeEvent : utils.addEvent,
            target = this.options.bindToWrapper ? this.wrapper : window;

        eventType(window, 'orientationchange', this);
        eventType(window, 'resize', this);

        if ( this.options.click ) {
            eventType(this.wrapper, 'click', this, true);
        }

        if ( !this.options.disableMouse ) {
            eventType(this.wrapper, 'mousedown', this);
            eventType(target, 'mousemove', this);
            eventType(target, 'mousecancel', this);
            eventType(target, 'mouseup', this);
        }

        if ( utils.hasPointer && !this.options.disablePointer ) {
            eventType(this.wrapper, utils.prefixPointerEvent('pointerdown'), this);
            eventType(target, utils.prefixPointerEvent('pointermove'), this);
            eventType(target, utils.prefixPointerEvent('pointercancel'), this);
            eventType(target, utils.prefixPointerEvent('pointerup'), this);
        }

        if ( utils.hasTouch && !this.options.disableTouch ) {
            eventType(this.wrapper, 'touchstart', this);
            eventType(target, 'touchmove', this);
            eventType(target, 'touchcancel', this);
            eventType(target, 'touchend', this);
        }

        eventType(this.scroller, 'transitionend', this);
        eventType(this.scroller, 'webkitTransitionEnd', this);
        eventType(this.scroller, 'oTransitionEnd', this);
        eventType(this.scroller, 'MSTransitionEnd', this);
    }

对各种事件进行注册或者取消,这里要注意的是addEvent在前面说过是util的方法,第3个参数this指向的是iscroll本身,按理来说注册事件监听应该传入一个回调函数

而MDN的文档element.addEventListener告诉我们如果传入的是一个object对象,该参数必是实现EventListener接口的一个对象,即拥有handleEvent方法

handleEvent方法

handleEvent: function (e) {  
        switch ( e.type ) {
            case 'touchstart':
            case 'pointerdown':
            case 'MSPointerDown':
            case 'mousedown':
                this._start(e);
                break;
            case 'touchmove':
            case 'pointermove':
            case 'MSPointerMove':
            case 'mousemove':
                this._move(e);
                break;
            case 'touchend':
            case 'pointerup':
            case 'MSPointerUp':
            case 'mouseup':
            case 'touchcancel':
            case 'pointercancel':
            case 'MSPointerCancel':
            case 'mousecancel':
                this._end(e);
                break;
            case 'orientationchange':
            case 'resize':
                this._resize();
                break;
            case 'transitionend':
            case 'webkitTransitionEnd':
            case 'oTransitionEnd':
            case 'MSTransitionEnd':
                this._transitionEnd(e);
                break;
            case 'wheel':
            case 'DOMMouseScroll':
            case 'mousewheel':
                this._wheel(e);
                break;
            case 'keydown':
                this._key(e);
                break;
            case 'click':
                if ( !e._constructed ) {
                    e.preventDefault();
                    e.stopPropagation();
                }
                break;
        }
    }

switch...case语句告诉我们,event会从上往下找到对应的函数来处理

_start方法

如果是鼠标操作,必须是左键,阻止默认的行为,除非一些输入框等元素

// React to left mouse button only
        //
        if ( utils.eventType[e.type] != 1 ) {
            if ( e.button !== 0 ) {
                return;
            }
        }

        if ( !this.enabled || (this.initiated && utils.eventType[e.type] !== this.initiated) ) {
            return;
        }
        //
        if ( this.options.preventDefault && !utils.isBadAndroid && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) {
            e.preventDefault();
        }
        var point = e.touches ? e.touches[0] : e,
            pos;//获得接触点的对象

        this.initiated  = utils.eventType[e.type];
        this.moved      = false;
        this.distX      = 0;
        this.distY      = 0;
        this.directionX = 0;
        this.directionY = 0;
        this.directionLocked = 0;

        this._transitionTime();//未传任何参数的情况下动画时间为0,意味着没有动画

        this.startTime = utils.getTime();//记录当前时间
        //如果处于transition中说明这不是第一次触碰,因为滚动的时候可能是不断的往某个方向滑动
        if ( this.options.useTransition && this.isInTransition ) {
            this.isInTransition = false;
            pos = this.getComputedPosition();//得到当前动画过程中将要结束的位置
            this._translate(Math.round(pos.x), Math.round(pos.y));//停止动画,马上移动到动画要结束的位置
            this._execEvent('scrollEnd');
        } else if ( !this.options.useTransition && this.isAnimating ) {//如果不支持transition降级动画
            this.isAnimating = false;
            this._execEvent('scrollEnd');
        }

        this.startX    = this.x;
        this.startY    = this.y;
        this.absStartX = this.x;
        this.absStartY = this.y;
        this.pointX    = point.pageX;
        this.pointY    = point.pageY;

        this._execEvent('beforeScrollStart');

这里要注意的是对于先设置transitionDuration为0,在translate到指定地点的过程会中断当前正在进行的动画,并瞬间移到指定的坐标,这个处理的意义在于当iscroll处于滑动动画过程中,手指再次触碰的时候默认是认为你想要停止滑动,就好像踩刹车一样

_move函数

计算坐标

    if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) {
            return;
        }

        if ( this.options.preventDefault ) {    
            e.preventDefault();
        }

        var point       = e.touches ? e.touches[0] : e,
            deltaX      = point.pageX - this.pointX,
            deltaY      = point.pageY - this.pointY,
            timestamp   = utils.getTime(),
            newX, newY,
            absDistX, absDistY;

        this.pointX     = point.pageX;
        this.pointY     = point.pageY;

        this.distX      += deltaX;
        this.distY      += deltaY;
        absDistX        = Math.abs(this.distX);
        absDistY        = Math.abs(this.distY);

如果上次滚动endTime与这次开始时间间隔大于300ms(类似手一直停在一个位置不松手)或者移动距离小于10px,

        if ( timestamp - this.endTime > 300 && (absDistX < 10 && absDistY < 10) ) {
            return;
        }

        // If you are scrolling in one direction lock the other
        //判断是否是双方向滚动还是单方向
        if ( !this.directionLocked && !this.options.freeScroll ) {
            if ( absDistX > absDistY + this.options.directionLockThreshold ) {
                this.directionLocked = 'h';     // lock horizontally
            } else if ( absDistY >= absDistX + this.options.directionLockThreshold ) {
                this.directionLocked = 'v';     // lock vertically
            } else {
                this.directionLocked = 'n';     // no lock
            }
        }

        if ( this.directionLocked == 'h' ) {
            if ( this.options.eventPassthrough == 'vertical' ) {
                e.preventDefault();
            } else if ( this.options.eventPassthrough == 'horizontal' ) {
                this.initiated = false;
                return;
            }

            deltaY = 0;
        } else if ( this.directionLocked == 'v' ) {
            if ( this.options.eventPassthrough == 'horizontal' ) {
                e.preventDefault();
            } else if ( this.options.eventPassthrough == 'vertical' ) {
                this.initiated = false;
                return;
            }

            deltaX = 0;
        }

        deltaX = this.hasHorizontalScroll ? deltaX : 0;
        deltaY = this.hasVerticalScroll ? deltaY : 0;

        newX = this.x + deltaX;
        newY = this.y + deltaY;

        // Slow down if outside of the boundaries
        //如果移动的地方超过边界会有减速弹回来动画效果,这里记录坐标
        if ( newX > 0 || newX < this.maxScrollX ) {
            newX = this.options.bounce ? this.x + deltaX / 3 : newX > 0 ? 0 : this.maxScrollX;
        }
        if ( newY > 0 || newY < this.maxScrollY ) {
            newY = this.options.bounce ? this.y + deltaY / 3 : newY > 0 ? 0 : this.maxScrollY;
        }

        this.directionX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0;
        this.directionY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0;

        if ( !this.moved ) {
            //触发滚动开始事件,这些事件都是自定义的,给使用iscroll的开发者注册使用
            this._execEvent('scrollStart');
        }

        this.moved = true;
        //滚动到新的距离
        this._translate(newX, newY);



        if ( timestamp - this.startTime > 300 ) {
            this.startTime = timestamp;
            this.startX = this.x;
            this.startY = this.y;
        }

每300ms会重置一次当前位置以及开始时间,这个就是为什么我们在抓住不放很久突然丢开仍然有长距离移动的原因

作者:shaynegui
喜欢打德州,玩dota,听电音,web前端脑残粉
我的专栏 GitHub