码迷,mamicode.com
首页 > 其他好文 > 详细

iScroll学习笔记2--浅读源码

时间:2015-10-02 01:29:14      阅读:299      评论:0      收藏:0      [点我收藏+]

标签:

iscroll的架子是这样的

(function (window, document, Math){

    var utils = (function (){
        var me = {};
        // 扩展一些常用的工具方法为me的方法
        return me;
        
    }());

    function IScroll(el, options){
        // 初始化一些属性和状态
    }
    
    IScroll.prototype = {
        constructor: IScroll,
        // 主体方法都在这里

    }

}(window, document, Math))

 

来看看utils里有哪些好东西

var _elStyle = document.createElement(‘div‘).style;

// 取得浏览器前缀
var _vendor = (function (){
    var vendors = [‘t‘, ‘webkitT‘, ‘MozT‘, ‘msT‘, ‘OT‘],
        transform,
        i = 0,
        len = vendors.length;

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

// 每次用css3动画都要加上烦人的浏览器前缀,都靠它了
function _prefixStyle (style){
    if(_vendor === false) return false;
    if(_vendor === ‘‘) return style;  // transform
    return _vendor + style.charAt(0).toUpperCase() + style.substr(1);
}    


/**
 * 势能动画, touchEnd的时候触发, 计算手指(鼠标)离开之后运动的时间和距离
 * @param  {[type]} current     [当前鼠标位置]
 * @param  {[type]} start       [touchstart时的鼠标位置]
 * @param  {[type]} time        [touchstart到touchend所用的时间]
 * @param  {[type]} lowerMargin [边界距离]
 * @param  {[type]} wrapperSize [容器的高度]
 * @return {[type]}             [返回一个包含距离和时间两个属性的对象]
 */
me.momentum = function (current, start, time, lowerMargin, wrapperSize){
    var distance = current -start,
        speed = Math.abs(distance) / time,
        destination,
        duration,
        deceleration = 0.0006;
    // v * (0-v)/2 * a 表示 速度由v到0所运动的距离
    destination = current + (speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 );
    // t = v / a;
    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
    };
};

momentum这个函数相当好,用到了一点点物理知识,嘿嘿,花了点时间才理解是怎么回事

utils还扩展了一些功能性判定的函数,一起来看看

// 获取当前浏览器支持的transform属性,保存在变量中
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));

// 保存 transform 和 transition 属性到 style属性下,方便后面操作, 这种思路值得学习
me.extend(me.style = {}, {
  transform: _transform,
  transitionTimingFunction: _prefixStyle(‘transitionTimingFunction‘),
  transitionDuration: _prefixStyle(‘transitionDuration‘),
  transitionDelay: _prefixStyle(‘transitionDelay‘),
  transformOrigin: _prefixStyle(‘transformOrigin‘)
});

还有一些,暂时不太懂,以后再补上

构造函数

在构造函数中,主要是初始化了一些配置参数和属性,以及初始状态

function IScroll (el, options) {
  this.wrapper = typeof el == ‘string‘ ? document.querySelector(el) : el;
  this.scroller = this.wrapper.children[0];
// 缓存style以提高性能...
  this.scrollerStyle = this.scroller.style;   // cache style for better performance

  this.options = {

    resizeScrollbars: true,

    mouseWheelSpeed: 20,

    snapThreshold: 0.334,

// INSERT POINT: OPTIONS 

    startX: 0,
    startY: 0,
    scrollY: true,
    directionLockThreshold: 5,
    momentum: true,

    bounce: true,
    bounceTime: 600,
    bounceEasing: ‘‘,

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

    HWCompositing: true,
    useTransition: true,
    useTransform: true
  };
// 这里居然没用到 utils封装的 extend
  for ( var i in options ) {
    this.options[i] = options[i];
  }

  // Some defaults  
  this.x = 0;
  this.y = 0;
  this.directionX = 0;
  this.directionY = 0;
  this._events = {};

// INSERT POINT: DEFAULTS

  this._init();
  this.refresh();

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

构造函数里调用了4个方法  _init ,  refresh ,  scrollTo,  enable 来看看它们都干了什么

this.enabled 是个全局开关

enable: function () {
    this.enabled = true;
}
 
// 构造函数里调用该函数并没有传第三个参数, 默认是用css3动画
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);
    }
}

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


  if ( this.indicators ) {
    for ( var i = this.indicators.length; i--; ) {
      this.indicators[i].updatePosition();
    }
  }


// INSERT POINT: _translate

}

 

refresh: function () {

// 这里获取wrapper的offsetHeight只是为了强制浏览器进行重排
    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;

    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;

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

    this._execEvent(‘refresh‘);

    this.resetPosition();

// INSERT POINT: _refresh

  }

 

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

    eventType(this.wrapper, ‘touchstart‘, this);
    eventType(target, ‘touchmove‘, this);
    eventType(target, ‘touchcancel‘, this);
    eventType(target, ‘touchend‘, this);
},
handleEvent: function (ev){
    switch (ev.type){
        case ‘touchstart‘:
            this._start(ev);
            break;
        case ‘touchmove‘:
            this._move(ev);
            break;
        case ‘touchend‘:
            this._end(ev);
            break;
    }
}

这里有一个知识点要提到,就是addEventListener/removeEventListener的第二个参数,也就是上面eventType的第三个参数,这里传递的是整个IScroll对象,而不是一个事件处理函数。

当传递的是一个对象时,会执行该对象下的 handleEvent方法,该方法就是真正的事件处理函数。具体可以参考这里

核心三大事件处理函数

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

    this.initialed  = utils.eventType[ev.type];
    this.moved      = false;
    // 偏移尺寸
    this.distX      = 0;
    this.distY      = 0;
    
    this.startTime  = utils.getTime();

    // 如果当前动画没有停止,则停止动画
    if(this.isInTransition){
        this.isInTransition = false;
        pos                 = this.getComputedPosition();
        this._translate(Math.round(pos.x), Math.round(pos.y));
        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;
},
_move: function (ev){     

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

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

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

    // 保存移动的尺寸大小
    this.distX += deltaX;
    this.distY += deltaY;

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

    // 至少移动10px
    if( timestamp - this.endTime > 300 && (absDistX < 10 && absDistY < 10)){
        return;
    }

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

    // 超出边界减小拖动的速度
    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.x + deltaY / 3 : newY > 0 ? 0 : this.maxScrollY;
    }
    // 执行scrollStart用户注册函数
    if(!this.moved){
        this._execEvent(‘scrollStart‘);
    }

    this.moved = true;
    // 移动 this.scroller
    this._translate(newX, newY);

    // 每300ms 重新更新起点坐标和起始时间
    if(timeStamp - this.startTime > 300){
        this.startTime = timeStamp;
        this.startX = this.x;
        this.startY = this.y;
    }
},
_end: function (ev){
    var point = e.changedTouches ? e.changedTouches[0] : ev,
        momentumX,
        momentumY,
        duration  = utils.getTime() - this.startTime,  // 这里不是接触屏幕的时间, 因为每300秒会重置一次
         newX      = Math.round(this.x),
        newY      = Math.round(this.y),
        distanceX = Math.abs(newX - this.startX),
        distanceY = Math.abs(newY - this.startY),
        time      = 0;

    this.isInTransition = 0;
    this.initiatd = 0;
    this.endTime = utils.getTime();

    this.scrollTo(newX, newY);   // 确保最后的位置是整数.

    // 计算手指离开屏幕后继续滑动的距离
    if( this.optionsmomentum && duration < 300 ){
        momentumX = this.utils.momentum(this.x, this.startX, duration, this.maxScrollX, 0, this.options.deceleration);
        momentumY = this.utils.momentum(this.y, this.startY, duration, this.maxScrollY, 0, this.options.deceleration);
        newX = momentumX.destination;
        newY = momentumY.destination;
        time = Math.max(momentumX.duration, momentumY.duration);
    }

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

    this._execEvent(‘scrollEnd‘);

}

只是粗浅地看了看源码,一方面自己功力不够,很多地方看不太懂,两一方面没有在实战中用过,更加无法体会这个库的精妙之处。但是已经感觉受益良多,可见这个库相当之优秀,日后一定认真研究一番。

iScroll学习笔记2--浅读源码

标签:

原文地址:http://www.cnblogs.com/walle2/p/4851791.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!