iscroll-lite.js源码注释

/*! iScroll v5.1.2 ~ (c) 2008-2014 Matteo Spinelli ~ http://cubiq.org/license */

(function (window, document, Math) {

    //请求动画帧

var rAF = window.requestAnimationFrame    ||

    window.webkitRequestAnimationFrame    ||

    window.mozRequestAnimationFrame        ||

    window.oRequestAnimationFrame        ||

    window.msRequestAnimationFrame        ||

    function (callback) { window.setTimeout(callback, 1000 / 60); };

//显示器16.7ms刷新间隔之前发生了其他绘制请求(setTimeout),导致所有帧丢失,setTimeout的定时器值推荐最小使用16.7ms的原因(16.7 = 1000 / 60, 即每秒60帧)

//requestAnimationFrame与setTimeout的不同在于,前者跟着浏览器重绘时间走,不存在后者丢帧的问题

var utils = (function () {

    var me = {};



    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);

    }

    //获取当前时间

    me.getTime = Date.now || function getTime () { return new Date().getTime(); };

    //对象复制

    me.extend = function (target, obj) {

        for ( var i in obj ) {

            target[i] = obj[i];

        }

    };

    //事件

    me.addEvent = function (el, type, fn, capture) {

        el.addEventListener(type, fn, !!capture);

    };



    me.removeEvent = function (el, type, fn, capture) {

        el.removeEventListener(type, fn, !!capture);

    };

    //MSPointerEvent 指针事件是一些事件和相关接口,用于处理来自鼠标、手写笔或触摸屏等设备的硬件不可知的指针输入,IE10引入,IE11去掉前缀,其他浏览器都不支持指针事件。

    me.prefixPointerEvent = function (pointerEvent) {

        return window.MSPointerEvent ? 

            'MSPointer' + pointerEvent.charAt(9).toUpperCase() + pointerEvent.substr(10):

            pointerEvent;

    };

    //deceleration减速

    /**

     * 根据我们的拖动返回运动的长度与耗时,用于惯性拖动判断

     * @param current 当前鼠标位置

     * @param start touchStart时候记录的Y(可能是X)的开始位置,但是在touchmove时候可能被重写

     * @param time touchstart到手指离开时候经历的时间,同样可能被touchmove重写

     * @param lowerMargin y可移动的最大距离,这个一般为计算得出 this.wrapperHeight - this.scrollerHeight

     * @param wrapperSize 如果有边界距离的话就是可拖动,不然碰到0的时候便停止

     * @param deceleration 匀减速

     * @returns {{destination: number, duration: number}}

     */

    me.momentum = function (current, start, time, lowerMargin, wrapperSize, deceleration) {

        var distance = current - start,

            speed = Math.abs(distance) / time,

            destination,

            duration;

        //减速变量

        deceleration = deceleration === undefined ? 0.0006 : deceleration;

        //减速路程

        destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 );

        //持续时间

        duration = speed / deceleration;



        if ( destination < lowerMargin ) {

            destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin;

            distance = Math.abs(destination - current);

            duration = distance / speed;

        } else if ( destination > 0 ) {

            destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0;

            distance = Math.abs(current) + destination;

            duration = distance / speed;

        }



        return {

            destination: Math.round(destination),

            duration: duration

        };

    };



    var _transform = _prefixStyle('transform');



    me.extend(me, {

        hasTransform: _transform !== false,

        hasPerspective: _prefixStyle('perspective') in _elementStyle, //透视

        hasTouch: 'ontouchstart' in window,

        hasPointer: window.PointerEvent || window.MSPointerEvent, // IE10 is prefixed

        hasTransition: _prefixStyle('transition') in _elementStyle

    });



    // This should find all Android browsers lower than build 535.19 (both stock browser and webview) 低版本安卓机

    me.isBadAndroid = /Android /.test(window.navigator.appVersion) && !(/Chrome\/\d/.test(window.navigator.appVersion));



    me.extend(me.style = {}, {

        transform: _transform,

        transitionTimingFunction: _prefixStyle('transitionTimingFunction'),

        transitionDuration: _prefixStyle('transitionDuration'),

        transitionDelay: _prefixStyle('transitionDelay'),

        transformOrigin: _prefixStyle('transformOrigin')

    });



    me.hasClass = function (e, c) {

        var re = new RegExp("(^|\\s)" + c + "(\\s|$)");

        return re.test(e.className);

    };



    me.addClass = function (e, c) {

        if ( me.hasClass(e, c) ) {

            return;

        }



        var newclass = e.className.split(' ');

        newclass.push(c);

        e.className = newclass.join(' ');

    };



    me.removeClass = function (e, c) {

        if ( !me.hasClass(e, c) ) {

            return;

        }



        var re = new RegExp("(^|\\s)" + c + "(\\s|$)", 'g');

        e.className = e.className.replace(re, ' ');

    };



    me.offset = function (el) {

        var left = -el.offsetLeft,

            top = -el.offsetTop;



        // jshint -W084

        while (el = el.offsetParent) {

            left -= el.offsetLeft;

            top -= el.offsetTop;

        }

        // jshint +W084



        return {

            left: left,

            top: top

        };

    };

    // 配合config里面的preventDefaultException属性,不对匹配到的element使用e.preventDefault()。

    me.preventDefaultException = function (el, exceptions) {

        for ( var i in exceptions ) {

            if ( exceptions[i].test(el[i]) ) {

                return true;

            }

        }



        return false;

    };

    // 事件类型

    me.extend(me.eventType = {}, {

        touchstart: 1,

        touchmove: 1,

        touchend: 1,



        mousedown: 2,

        mousemove: 2,

        mouseup: 2,



        pointerdown: 3,

        pointermove: 3,

        pointerup: 3,



        MSPointerDown: 3,

        MSPointerMove: 3,

        MSPointerUp: 3

    });

    // 缓动函数

    me.extend(me.ease = {}, {

        quadratic: {

            style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',

            fn: function (k) {

                return k * ( 2 - k );

            }

        },

        circular: {

            style: 'cubic-bezier(0.1, 0.57, 0.1, 1)',    // Not properly "circular" but this looks better, it should be (0.075, 0.82, 0.165, 1)

            fn: function (k) {

                return Math.sqrt( 1 - ( --k * k ) );

            }

        },

        back: {

            style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',

            fn: function (k) {

                var b = 4;

                return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1;

            }

        },

        bounce: {

            style: '',

            fn: function (k) {

                if ( ( k /= 1 ) < ( 1 / 2.75 ) ) {

                    return 7.5625 * k * k;

                } else if ( k < ( 2 / 2.75 ) ) {

                    return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75;

                } else if ( k < ( 2.5 / 2.75 ) ) {

                    return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375;

                } else {

                    return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;

                }

            }

        },

        elastic: {

            style: '',

            fn: function (k) {

                var f = 0.22,

                    e = 0.4;



                if ( k === 0 ) { return 0; }

                if ( k == 1 ) { return 1; }



                return ( e * Math.pow( 2, - 10 * k ) * Math.sin( ( k - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 );

            }

        }

    });



    me.tap = function (e, eventName) {

        var ev = document.createEvent('Event');

        /**

        只有在新创建的 Event 对象被 Document 对象或 Element 对象的 dispatchEvent() 方法分派之前,才能调用 Event.initEvent() 方法        

        初始化新事件对象的属性

        */

        ev.initEvent(eventName, true, true);

        ev.pageX = e.pageX;

        ev.pageY = e.pageY;

        //事件触发器就是用来触发某个元素下的某个事件

        e.target.dispatchEvent(ev);

    };



    me.click = function (e) {

        var target = e.target,

            ev;

        //不是select/input/textarea就创建事件

        if ( !(/(SELECT|INPUT|TEXTAREA)/i).test(target.tagName) ) {

            ev = document.createEvent('MouseEvents');

            ev.initMouseEvent('click', true, true, e.view, 1,

                target.screenX, target.screenY, target.clientX, target.clientY,

                e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,

                0, null);



            ev._constructed = true;

            target.dispatchEvent(ev);

        }

    };



    return me;

})();



function IScroll (el, options) {

    //传入的元素 必须要有子元素

    this.wrapper = typeof el == 'string' ? document.querySelector(el) : el;

    //子元素 实际滑动元素

    this.scroller = this.wrapper.children[0];

    this.scrollerStyle = this.scroller.style;        // cache style for better performance



    this.options = {



// INSERT POINT: OPTIONS 



        startX: 0,

        startY: 0,

        //默认是Y轴上下滚动

                scrollY: true,

                //方向锁定阈值,比如用户点击屏幕后,x与y之间差距大于5px,判断用户的拖动意图,是x方向拖动还是y方向

                directionLockThreshold: 5,

                //是否有惯性缓冲动画

        momentum: true,

        //超出边界时候是否还能拖动

                bounce: true,

                //超出边界还原时间点

                bounceTime: 600,

                //超出边界返回的动画

                bounceEasing: '',

                //是否阻止默认滚动事件

                preventDefault: true,

                //当遇到表单元素则不阻止冒泡,而是弹出系统自带相应的输入控件

                preventDefaultException: { tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/ },

        //硬件合成 通过GPU做图形渲染

        HWCompositing: true,

        //使用transition

        useTransition: true,

        //使用transform

        useTransform: true

    };



    for ( var i in options ) {

        this.options[i] = options[i];

    }



    // Normalize options

    this.translateZ = this.options.HWCompositing && utils.hasPerspective ? ' translateZ(0)' : '';



    this.options.useTransition = utils.hasTransition && this.options.useTransition;

    this.options.useTransform = utils.hasTransform && this.options.useTransform;

    

    // 默认false,当你想保留原生垂直滚动但能够添加水平iscroll时候,设置为true,iscroll区域水平滑动时候的touch垂直移动,不会触发原生的垂直滑动

    // 通常设置为:{eventPassthrough:true,scrollX:true,scrollY:false}

    // 

    this.options.eventPassthrough = this.options.eventPassthrough === true ? 'vertical' : this.options.eventPassthrough;

    //当设置了eventPassthrough时候,不阻止默认的滚动事件

    this.options.preventDefault = !this.options.eventPassthrough && this.options.preventDefault;



    // If you want eventPassthrough I have to lock one of the axes

    // eventPassthrough可以传入具体名字(vertical/horizontal),这里更好说明了eventPassthrough的作用,对于传入的值来阻止相应轴的滑动事件

    // eventPassthrough -> true -> vertical -> scrollY : false 垂直滚动忽略

    this.options.scrollY = this.options.eventPassthrough == 'vertical' ? false : this.options.scrollY;

    this.options.scrollX = this.options.eventPassthrough == 'horizontal' ? false : this.options.scrollX;



    // With eventPassthrough we also need lockDirection mechanism

    // freeScroll只能与scrollX一起使用 {scrollX:true,freeScroll:true}

    this.options.freeScroll = this.options.freeScroll && !this.options.eventPassthrough;

    this.options.directionLockThreshold = this.options.eventPassthrough ? 0 : this.options.directionLockThreshold;



    // 缓动函数

    this.options.bounceEasing = typeof this.options.bounceEasing == 'string' ? utils.ease[this.options.bounceEasing] || utils.ease.circular : this.options.bounceEasing;

    

    //window resize时重新获取位置

    this.options.resizePolling = this.options.resizePolling === undefined ? 60 : this.options.resizePolling;



    if ( this.options.tap === true ) {

        this.options.tap = 'tap';

    }



// INSERT POINT: NORMALIZATION



    // Some defaults    

    this.x = 0;

    this.y = 0;

    this.directionX = 0;

    this.directionY = 0;

    this._events = {};



// INSERT POINT: DEFAULTS

    // IScroll初始化

    this._init();

    // 

    this.refresh();



    // 定位到最顶最左

    this.scrollTo(this.options.startX, this.options.startY);

    // 代表“启用”的一个标志

    this.enable();

}



IScroll.prototype = {

    version: '5.1.2',



    _init: function () {

        this._initEvents();



// INSERT POINT: _init



    },



    destroy: function () {

        this._initEvents(true);



        this._execEvent('destroy');

    },



    _transitionEnd: function (e) {

        if ( e.target != this.scroller || !this.isInTransition ) {

            return;

        }



        this._transitionTime();

        if ( !this.resetPosition(this.options.bounceTime) ) {

            this.isInTransition = false;

            this._execEvent('scrollEnd');

        }

    },



    _start: function (e) {

        // 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;

        }

        // 如果 preventDefault === true 且 不是落后的安卓版本 且 不是需要过滤的target 就 阻止默认的行为

        if ( this.options.preventDefault && !utils.isBadAndroid && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) {

            e.preventDefault();

        }

        //e.touches表示的在屏幕上所有的触摸点,但事实上,绝大数手机浏览器并不支持多点触摸,所有用e.touchees[0]捕获一个触点就知足吧,

        //不要再奢望获取e.touches[>0]了,这个触点的位置可以有e.touches[0].pageX获取页面x坐标(像素);

        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;    //x方向移动数

        this.directionY = 0;    //y方向移动数

        this.directionLocked = 0;    //方向锁

        // 设置运动时间

        this._transitionTime();

        // 开始时间

        this.startTime = utils.getTime();



        // 如果还在运动中

        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 ) {

            this.isAnimating = false;

            this._execEvent('scrollEnd');

        }



        this.startX    = this.x;        // scroller开始位置x

        this.startY    = this.y;        // scroller开始位置y

        this.absStartX = this.x;        //

        this.absStartY = this.y;        //

        this.pointX    = point.pageX;    // 触点x

        this.pointY    = point.pageY;    // 触点y

        // 触发滑动前回调函数

        this._execEvent('beforeScrollStart');

    },



    _move: function (e) {

        // 禁止 or 不存在此eventType 则返回

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

            return;

        }



        if ( this.options.preventDefault ) {    // increases performance on Android? TODO: check!

            e.preventDefault();

        }

        // point 触点

        var point        = e.touches ? e.touches[0] : e,

            deltaX        = point.pageX - this.pointX,    // 当前触点的pagex - 开始时的pagex = 触点档次增量x

            deltaY        = point.pageY - this.pointY,    // 触点增量y

            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);



        // We need to move at least 10 pixels for the scrolling to initiate

        // 触点至少移动10px才会触发scroll的move 并且 移动大于300ms

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

            return;

        }



        // If you are scrolling in one direction lock the other

        // 除非设置了freeScroll,否则将只允许同一时间一个方向滑动

        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;

        // this.x this.y 是最近上一次的scroller位置

        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;    // -1 手势向左   1 手势向右

        this.directionY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0; // -1 手势向上   1 手势向下



        if ( !this.moved ) {

            this._execEvent('scrollStart');

        }



        this.moved = true;



        this._translate(newX, newY);



/* REPLACE START: _move */



        if ( timestamp - this.startTime > 300 ) {

            //300ms更新一次

            this.startTime = timestamp;

            this.startX = this.x;

            this.startY = this.y;

        }



/* REPLACE END: _move */



    },

    // touchEnd时候处理缓动

    _end: function (e) {

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

            return;

        }



        if ( this.options.preventDefault && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) {

            e.preventDefault();

        }



        var point = e.changedTouches ? e.changedTouches[0] : e,

            momentumX,

            momentumY,

            duration = utils.getTime() - this.startTime,

            newX = Math.round(this.x),

            newY = Math.round(this.y),

            distanceX = Math.abs(newX - this.startX),    //单次移动过程的x轴移动距离

            distanceY = Math.abs(newY - this.startY),

            time = 0,

            easing = '';



        this.isInTransition = 0;

        this.initiated = 0;

        this.endTime = utils.getTime();



        // reset if we are outside of the boundaries

        // 超过了边界就重新回到边界位

        if ( this.resetPosition(this.options.bounceTime) ) {

            return;

        }

        // 坚持滑动到

        this.scrollTo(newX, newY);    // ensures that the last position is rounded



        // we scrolled less than 10 pixels

        if ( !this.moved ) {

            if ( this.options.tap ) {

                utils.tap(e, this.options.tap);

            }



            if ( this.options.click ) {

                utils.click(e);

            }



            this._execEvent('scrollCancel');

            return;

        }



        if ( this._events.flick && duration < 200 && distanceX < 100 && distanceY < 100 ) {

            this._execEvent('flick');

            return;

        }



        // start momentum animation if needed

        // 获取缓动的距离 和 时间 { destination , duration }

        if ( this.options.momentum && duration < 300 ) {

            momentumX = this.hasHorizontalScroll ? utils.momentum(this.x, this.startX, duration, this.maxScrollX, this.options.bounce ? this.wrapperWidth : 0, this.options.deceleration) : { destination: newX, duration: 0 };

            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 };

            newX = momentumX.destination;

            newY = momentumY.destination;

            time = Math.max(momentumX.duration, momentumY.duration);

            this.isInTransition = 1;

        }



// INSERT POINT: _end



        if ( newX != this.x || newY != this.y ) {

            // change easing function when scroller goes out of the boundaries

            if ( newX > 0 || newX < this.maxScrollX || newY > 0 || newY < this.maxScrollY ) {

                easing = utils.ease.quadratic;

            }



            this.scrollTo(newX, newY, time, easing);

            return;

        }



        this._execEvent('scrollEnd');

    },



    _resize: function () {

        var that = this;



        clearTimeout(this.resizeTimeout);



        this.resizeTimeout = setTimeout(function () {

            that.refresh();

        }, this.options.resizePolling);

    },

    /**

     * 重新定位

     */

    resetPosition: function (time) {

        var x = this.x,

            y = this.y;



        time = time || 0;

        

        /**

        * 如果禁止水平滑动或者x大于0,x=0。如果可以滑动的情况,x应该是小于0的。

        * 否则(可水平滑动),x为maxScrollX即最左

        */

        if ( !this.hasHorizontalScroll || this.x > 0 ) {

            x = 0;

        } else if ( this.x < this.maxScrollX ) {

            x = this.maxScrollX;

        }

        /**

        * 如果禁止垂直滑动或者y大于0,y=0。如果可以滑动的情况,y应该是小于0的。

        * 否则(可垂直滑动),y为maxScrollY即最上

        */

        if ( !this.hasVerticalScroll || this.y > 0 ) {

            y = 0;

        } else if ( this.y < this.maxScrollY ) {

            y = this.maxScrollY;

        }



        // 初始化时候x y 都是0 返回。

        if ( x == this.x && y == this.y ) {

            return false;

        }



        this.scrollTo(x, y, time, this.options.bounceEasing);



        return true;

    },

    /**

     * scroll关闭的标志

     */

    disable: function () {

        this.enabled = false;

    },



    /**

     * scroll启用的标志

     */

    enable: function () {

        this.enabled = true;

    },



    /**

     * 重新获取位置等参数信息

     */

    refresh: function () {

        

        var rf = this.wrapper.offsetHeight;        // Force reflow

        // 获取包含块的宽度/高度

        this.wrapperWidth    = this.wrapper.clientWidth;

        this.wrapperHeight    = this.wrapper.clientHeight;



/* REPLACE START: refresh */

        // 滑动区的高度/宽度

        this.scrollerWidth    = this.scroller.offsetWidth;

        this.scrollerHeight    = this.scroller.offsetHeight;

        // 最多允许滑动的x/y

        this.maxScrollX        = this.wrapperWidth - this.scrollerWidth;

        this.maxScrollY        = this.wrapperHeight - this.scrollerHeight;



/* REPLACE END: refresh */

        // 是否可以垂直/水平滑动

        this.hasHorizontalScroll    = this.options.scrollX && this.maxScrollX < 0;

        this.hasVerticalScroll        = this.options.scrollY && this.maxScrollY < 0;



        if ( !this.hasHorizontalScroll ) {

            this.maxScrollX = 0;

            this.scrollerWidth = this.wrapperWidth;

        }



        if ( !this.hasVerticalScroll ) {

            this.maxScrollY = 0;

            this.scrollerHeight = this.wrapperHeight;

        }



        this.endTime = 0;

        this.directionX = 0;

        this.directionY = 0;

        

        // 获取包含块的offsetLeft offsetTop

        this.wrapperOffset = utils.offset(this.wrapper);



        this._execEvent('refresh');



        this.resetPosition();



// INSERT POINT: _refresh



    },

    /**

     * 自定义事件三件套,不解释。

     */

    on: function (type, fn) {

        if ( !this._events[type] ) {

            this._events[type] = [];

        }



        this._events[type].push(fn);

    },



    off: function (type, fn) {

        if ( !this._events[type] ) {

            return;

        }



        var index = this._events[type].indexOf(fn);



        if ( index > -1 ) {

            this._events[type].splice(index, 1);

        }

    },



    _execEvent: function (type) {

        if ( !this._events[type] ) {

            return;

        }



        var i = 0,

            l = this._events[type].length;



        if ( !l ) {

            return;

        }



        for ( ; i < l; i++ ) {

            this._events[type][i].apply(this, [].slice.call(arguments, 1));

        }

    },



    scrollBy: function (x, y, time, easing) {

        x = this.x + x;

        y = this.y + y;

        time = time || 0;



        this.scrollTo(x, y, time, easing);

    },



    /**

     *

     * @param  x 为移动的x轴坐标

     * @param y 为移动的y轴坐标

     * @param time 为移动时间

     * @param easing 为移动的动画效果

     */

    scrollTo: function (x, y, time, easing) {

        easing = easing || utils.ease.circular;



        this.isInTransition = this.options.useTransition && time > 0;



        if ( !time || (this.options.useTransition && easing.style) ) {

            this._transitionTimingFunction(easing.style);

            this._transitionTime(time);

            this._translate(x, y);

        } else {

            this._animate(x, y, time, easing.fn);

        }

    },



    /**

     * 这个方法实际上是对scrollTo的进一步封装,滚动到相应的元素区域。

     * @param el 为需要滚动到的元素引用

     * @param time 为滚动时间

     * @param offsetX 为X轴偏移量

     * @param offsetY 为Y轴偏移量

     * @param easing 动画效果

     */

    scrollToElement: function (el, time, offsetX, offsetY, easing) {

        el = el.nodeType ? el : this.scroller.querySelector(el);



        if ( !el ) {

            return;

        }



        var pos = utils.offset(el);



        pos.left -= this.wrapperOffset.left;

        pos.top  -= this.wrapperOffset.top;



        // if offsetX/Y are true we center the element to the screen

        if ( offsetX === true ) {

            offsetX = Math.round(el.offsetWidth / 2 - this.wrapper.offsetWidth / 2);

        }

        if ( offsetY === true ) {

            offsetY = Math.round(el.offsetHeight / 2 - this.wrapper.offsetHeight / 2);

        }



        pos.left -= offsetX || 0;

        pos.top  -= offsetY || 0;



        pos.left = pos.left > 0 ? 0 : pos.left < this.maxScrollX ? this.maxScrollX : pos.left;

        pos.top  = pos.top  > 0 ? 0 : pos.top  < this.maxScrollY ? this.maxScrollY : pos.top;



        time = time === undefined || time === null || time === 'auto' ? Math.max(Math.abs(this.x-pos.left), Math.abs(this.y-pos.top)) : time;



        this.scrollTo(pos.left, pos.top, time, easing);

    },

    // 动画运动时间

    _transitionTime: function (time) {

        time = time || 0;



        this.scrollerStyle[utils.style.transitionDuration] = time + 'ms';

        // 落后的安卓机运动時間需要把0转0.001s

        if ( !time && utils.isBadAndroid ) {

            this.scrollerStyle[utils.style.transitionDuration] = '0.001s';

        }



// INSERT POINT: _transitionTime



    },



    _transitionTimingFunction: function (easing) {

        this.scrollerStyle[utils.style.transitionTimingFunction] = easing;



// INSERT POINT: _transitionTimingFunction



    },



    /**

     * @param  x 为移动的x轴坐标

     * @param  y 为移动的y轴坐标

     */

    _translate: function (x, y) {

        if ( this.options.useTransform ) {



/* REPLACE START: _translate */



            this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ;



/* REPLACE END: _translate */



        } else {

            x = Math.round(x);

            y = Math.round(y);

            this.scrollerStyle.left = x + 'px';

            this.scrollerStyle.top = y + 'px';

        }



        this.x = x;

        this.y = y;



// INSERT POINT: _translate



    },



    /**

     * @param  remove 是否解绑事件

     */

    _initEvents: function (remove) {

        var eventType = remove ? utils.removeEvent : utils.addEvent,

            // 是否绑定到wrapper元素上

            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);

        }

        // 如果没禁用鼠标,绑定鼠标事件,针对PC。

        if ( !this.options.disableMouse ) {

            eventType(this.wrapper, 'mousedown', this);

            eventType(target, 'mousemove', this);

            eventType(target, 'mousecancel', this);

            eventType(target, 'mouseup', this);

        }

        // 针对win phone的touch事件

        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);

        }

        //针对ios & Android的touch事件

        if ( utils.hasTouch && !this.options.disableTouch ) {

            eventType(this.wrapper, 'touchstart', this);

            eventType(target, 'touchmove', this);

            eventType(target, 'touchcancel', this);

            eventType(target, 'touchend', this);

        }

        // css3动画结束后的回调事件

        eventType(this.scroller, 'transitionend', this);

        eventType(this.scroller, 'webkitTransitionEnd', this);

        eventType(this.scroller, 'oTransitionEnd', this);

        eventType(this.scroller, 'MSTransitionEnd', this);

    },



    getComputedPosition: function () {

        //获取scroller的样式

        var matrix = window.getComputedStyle(this.scroller, null),

            x, y;

        //两种方式 transform 和 top/left ( transform在ios下,文本输入的光标fixed )

        if ( this.options.useTransform ) {

            matrix = matrix[utils.style.transform].split(')')[0].split(', ');

            x = +(matrix[12] || matrix[4]);

            y = +(matrix[13] || matrix[5]);

        } else {

            x = +matrix.left.replace(/[^-\d.]/g, '');

            y = +matrix.top.replace(/[^-\d.]/g, '');

        }

        //返回 (x ,y)  or (top ,left)

        return { x: x, y: y };

    },



    /**

     * @param  destx 为移动目的地的x轴坐标

     * @param  desty 为移动目的地的y轴坐标

     * @param  duration 为移动的时间

     * @param  easingFn 为移动缓动函数

     */

    _animate: function (destX, destY, duration, easingFn) {

        var that = this,

            startX = this.x,

            startY = this.y,

            startTime = utils.getTime(),

            destTime = startTime + duration;



        function step () {

            var now = utils.getTime(),

                newX, newY,

                easing;



            if ( now >= destTime ) {

                that.isAnimating = false;

                that._translate(destX, destY);



                if ( !that.resetPosition(that.options.bounceTime) ) {

                    that._execEvent('scrollEnd');

                }



                return;

            }



            now = ( now - startTime ) / duration;

            easing = easingFn(now);

            newX = ( destX - startX ) * easing + startX;

            newY = ( destY - startY ) * easing + startY;

            that._translate(newX, newY);



            if ( that.isAnimating ) {

                rAF(step);

            }

        }



        this.isAnimating = true;

        step();

    },

    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;

        }

    }

};

IScroll.utils = utils;



if ( typeof module != 'undefined' && module.exports ) {

    module.exports = IScroll;

} else {

    window.IScroll = IScroll;

}



})(window, document, Math);

 

你可能感兴趣的:(scroll)