• 【iScroll源码学习04】分离IScroll核心


    前言

    最近几天我们前前后后基本将iScroll源码学的七七八八了,文章中未涉及的各位就要自己去看了

    我们学习源码的目的绝不是学习人家的源码,而是由高手的代码里面学习思想,或者研究解决方案,就拿我们这次学习iScroll,我的目的就是

    “抄袭”,我今天就针对自己的需求来抄袭iScroll的源码,组成自己的库,然后用于项目,然后高高兴兴的装B.......

    关系图

    工具类

    第一,我们将iScroll的工具类搬离出来,但是我们系统是一定支持CSS3动画的,所以requestAnimationFrame方法我们就不予理睬了

    第二,因为我们的系统是依赖于zepto的,甚至还用到了backbone(但是backbone与underscore大有被移除的可能,所以只用zepto就好)

    属性总览

    _elementStyle

    用于后面检测CSS3 兼容属性

    _vendor

    CSS3 兼容前缀

    hasTouch

    是否支持touch事件

    style

    几个CSS3 动画属性,比如在chrome下面是这样的

    Object {
    transform: "webkitTransform", 
    transitionTimingFunction: "webkitTransitionTimingFunction",
    transitionDuration: "webkitTransitionDuration",
    transitionDelay: "webkitTransitionDelay", 
    transformOrigin: "webkitTransformOrigin"
    }

    eventType

    touch事件的话值就是1,mouse事件便是2,这个对后面有影响的

    ease

    这个是CSS3的动画参数,会形成动画曲线,各位自己去看吧

    _prefixStyle

    会用到的私有方法,会返回CSS3兼容前缀

    getTime

    获取当前时间戳

    addEvent

    为dom绑定事件,注意其中的fn是对象,具体处理在handleEvent里面

    removeEvent

    为dom注销事件,注意其中的fn是对象,具体处理在handleEvent里面

    momentum

    这个对象中最难的一个BUG,也很重要,他会根据我们的拖动返回运动的长度与耗时,这个是根据物理公式计算而出的,十分靠谱,哥是没看懂用什么公式的

    样式兼容

    于是我们开始吧,首先,因为各个浏览器问题,我们需要做CSS3动画的兼容

     1 var _elementStyle = document.createElement('div').style;
     2 //获得CSS3前缀
     3 var _vendor = (function () {
     4   var vendors = ['t', 'webkitT', 'MozT', 'msT', 'OT'];
     5   var transform;
     6   var i = 0;
     7   var l = vendors.length;
     8 
     9   for (; i < l; i++) {
    10     transform = vendors[i] + 'ransform';
    11     if (transform in _elementStyle) return vendors[i].substr(0, vendors[i].length - 1);
    12   }
    13   return false;
    14 })();

    这句代码会返回需要做兼容的CSS属性的前缀,然后提供一个方法特别的干这个事情:

    1 //获取样式(CSS3兼容)
    2 function _prefixStyle(style) {
    3   if (_vendor === false) return false;
    4   if (_vendor === '') return style;
    5   return _vendor + style.charAt(0).toUpperCase + style.substr(1);
    6 }

    获取当前时间戳

    获得当前时间戳这个方法比较简单,这个会在计算动画用到:

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

    动画数据

     1 me.momentum = function (current, start, time, lowerMargin, wrapperSize) {
     2       var distance = current - start,
     3         speed = Math.abs(distance) / time,
     4         destination,
     5         duration,
     6         deceleration = 0.0006;
     7 
     8       destination = current + (speed * speed) / (2 * deceleration) * (distance < 0 ? -1 : 1);
     9       duration = speed / deceleration;
    10 
    11       if (destination < lowerMargin) {
    12         destination = wrapperSize ? lowerMargin - (wrapperSize / 2.5 * (speed / 8)) : lowerMargin;
    13         distance = Math.abs(destination - current);
    14         duration = distance / speed;
    15       } else if (destination > 0) {
    16         destination = wrapperSize ? wrapperSize / 2.5 * (speed / 8) : 0;
    17         distance = Math.abs(current) + destination;
    18         duration = distance / speed;
    19       }
    20 
    21       return {
    22         destination: Math.round(destination),
    23         duration: duration
    24       };
    25     };

    这个方法尤其关键,是iScroll动画平滑的一大功臣,这个方法内部用到了物理一个计算速度的公式,我可耻的忘了是什么了,这里直接不予关注了,解释下参数即可

    current:当前鼠标位置
    start:touchStart时候记录的Y(可能是X)的开始位置,但是在touchmove时候可能被重写
    time: touchstart到手指离开时候经历的时间,同样可能被touchmove重写
    lowerMargin:y可移动的最大距离,这个一般为计算得出 this.wrapperHeight - this.scrollerHeight
    wrapperSize:如果有边界距离的话就是可拖动,不然碰到0的时候便停止

    这个动画方法,我们后面用到还需要再说下

    属性检测

     1 //我们暂时只判断touch 和 mouse即可
     2 me.extend(me.style = {}, {
     3   hasTouch: 'ontouchstart' in window,
     4   transform: _prefixStyle('transform'),
     5   transitionTimingFunction: _prefixStyle('transitionTimingFunction'),
     6   transitionDuration: _prefixStyle('transitionDuration'),
     7   transitionDelay: _prefixStyle('transitionDelay'),
     8   transformOrigin: _prefixStyle('transformOrigin')
     9 });
    10 
    11 me.extend(me.eventType = {}, {
    12   touchstart: 1,
    13   touchmove: 1,
    14   touchend: 1,
    15 
    16   mousedown: 2,
    17   mousemove: 2,
    18   mouseup: 2
    19 });
    20 
    21 me.extend(me.ease = {}, {
    22   quadratic: {
    23     style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
    24     fn: function (k) {
    25       return k * (2 - k);
    26     }
    27   },
    28   circular: {
    29     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)
    30     fn: function (k) {
    31       return Math.sqrt(1 - (--k * k));
    32     }
    33   },
    34   back: {
    35     style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
    36     fn: function (k) {
    37       var b = 4;
    38       return (k = k - 1) * k * ((b + 1) * k + b) + 1;
    39     }
    40   },
    41   bounce: {
    42     style: '',
    43     fn: function (k) {
    44       if ((k /= 1) < (1 / 2.75)) {
    45         return 7.5625 * k * k;
    46       } else if (k < (2 / 2.75)) {
    47         return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75;
    48       } else if (k < (2.5 / 2.75)) {
    49         return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375;
    50       } else {
    51         return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375;
    52       }
    53     }
    54   },
    55   elastic: {
    56     style: '',
    57     fn: function (k) {
    58       var f = 0.22,
    59         e = 0.4;
    60 
    61       if (k === 0) { return 0; }
    62       if (k == 1) { return 1; }
    63 
    64       return (e * Math.pow(2, -10 * k) * Math.sin((k - f / 4) * (2 * Math.PI) / f) + 1);
    65     }
    66   }
    67 });
    View Code

    ease时动画平滑度相关的东西,我们暂时用着,后面看看可以用zepto或者CSS3默认的属性以减少代码量,工具类到此为止

    IScroll

    IScroll类是我们的关键,贯穿其中的是两个事件机制:

    ① 原生的touch(鼠标)事件

    ② 系统自建的异步事件模型

    起始点是touchstart,其中的动画又有很多开关,我们下面会慢慢涉及到,这里先说下他的简单属性

    基本属性

    wrapper

    基本外壳,这个dom的style属性一般是 position: absolute; overflow: hidden;是我们内部滚动条的外壳

    scroller

    我们真正滚动的元素,我们拖动的就是他

    PS:这里要求必须传入这两个DOM结构,不然就是错

    scrollerStyle

    scroller 的 Style对象,通过set他的属性改变样式

    options

    设置的基本参数信息

    this.options = {
      //是否具有滚动条
      scrollbars: true,
      // 其实时期Y的位置
      startY: 0,
      //超出边界还原时间点
      bounceTime: 600,
      //超出边界返回的动画
      bounceEasing: utils.ease.circular,
    
      //超出边界时候是否还能拖动
      bounce: true,
    
      //当window触发resize事件60ms后还原
      resizePolling: 60,
      startX: 0,
      startY: 0
    };

    这里的startY值得关注,我这里其实是想将X相关属性去掉,但是还没有完全去掉,这个后面点重构代码时候搞掉吧

    x/y

    当前属性的x/y坐标,用于后面touch事件

    _events

    保存系统中注册的事件,这里我们做个统计,系统一个注册了这些事件:

    this.on('scrollEnd', function () {
      this.indicator.fade();
    });
    
    var scope = this;
    this.on('scrollCancel', function () {
      scope.indicator.fade();
    });
    
    this.on('scrollStart', function () {
      scope.indicator.fade(1);
    });
    
    this.on('beforeScrollStart', function () {
      scope.indicator.fade(1, true);
    });
    
    this.on('refresh', function () {
      scope.indicator.refresh();
    });

    ① scrollEnd

    这个事件在系统中主要用于改变滚动条的透明度

    在touchstart时候因为会停止动画所以会触发,但是因为touchstart结束后又会触发move所以滚动条还是会显示

    在touchend时候会停止动画,当然也会触发

    transitionend事件触发时候,动画真正的停止,scrollEnd也会触发

    ② scrollCancel

    当touchend时候若是认定是一次点击事件便会触发该事件,将滚动条透明度设置为透明

    ③ scrollStart

    当我们手指开始滑动时候,就会触发该事件,将滚动条透明度设置为可见

    ④ beforeScrollStart

    当手指触屏屏幕便会将透明度设置为非透明

    这个有一个判断点就是连续拖动才会设置非透明,如果首次触屏一定要拖动才会显示

    ⑤ refresh

    在触发系统refresh方法时候会该事件,这个事件会触发滚动条的refresh事件,改变其位置,以及相关的尺寸(因为滚动条的尺寸根据他而来)

    _initEvents

    这个为IScroll滚动区域设定了基本的DOM事件,有方向改变和尺寸改变的resize事件
    然后注册了停止动画时候触发的事件,以及基础三个事件点touchstart/touchmove/touchend

    _start/_move/_end

    touch时候执行的方法,这几个方法我们后面详细解析

    _resize

    当我们初始化时候会计算wrapper与scroller的尺寸,resize时候也会执行,这里不予关注

    _transitionTime

    该方法会设置运动时间,不传的话运动时间为0 ,即没有动画

    getComputedPosition

    获得一个DOM的实时样式样式,在touchstart时候保留DOM样式状态十分有用

    _initIndicator

    该方法用于初始化滚动条(这个方法位置其实该靠前),他会初始化一个滚动条,然后在自己五个事件状态下依次执行滚动条的一个方法

    _translate

    该方法比较重要
    他会根据传入的x,y值设置translate属性,然后就会产生动画,完了调用滚动条的updatePosition方法更新其位置

    resetPosition

    该方法用于当超出边界时候要还原scroller位置的方法

    scrollTo

    该方法比较关键,其实是由他调用_translate改变scroller具体位置,其中可以传入一些时间以及动画曲线的参数进行动画
    这个时候如果touchstart触屏了话便会停止动画,实现方式是_transitionTime()

    disable/enable

    统一的开关

    on

    这个用于注册事件,我们之前做过介绍

    _execEvent

    触发注册的事件

    destroy

    销毁注册的DOM事件

    _transitionEnd

    CSS3动画结束时候会触发的事件,这个里面会将isInTransition设置为false,这是一个动画的重要参数依据,我们后面要详细说

    handleEvent

    具体事件执行的地方,但是真正的逻辑又分散到了上述的各个方法

    至此,IScroll的属性就介绍完毕了,我们下面抽出核心的做介绍

    初始化方法

    refresh

    该方法其实应该属于系统第一步,这个方法会让缓存IScroll中的几个DOM尺寸,动画多是和尺寸打关系嘛

     1 refresh: function () {
     2   var rf = this.wrapper.offsetHeight;     // Force reflow
     3 
     4   this.wrapperHeight = this.wrapper.clientHeight;
     5   this.scrollerHeight = this.scroller.offsetHeight;
     6   this.maxScrollY = this.wrapperHeight - this.scrollerHeight;
     7 
     8   this.endTime = 0;
     9 
    10   var offset = $(this.wrapper).offset();
    11 
    12   this.wrapperOffset = {
    13     left: offset.left * (-1),
    14     top: offset.top * (-1)
    15   };
    16 
    17   this._execEvent('refresh');
    18 
    19   this.resetPosition();
    20 
    21 },

    首先做了一步操作让下面的操作不会发生重绘

    var rf = this.wrapper.offsetHeight;

    然后初始化了几个关键DOM的高度,并且获得maxScrollY(真正使用的时候会/3)

    接下来重置了endtime为0,并且设置一些基本参数后触发refresh事件(会触发滚动条的refresh)
    当然,如果超出了边界的话,会resetPosition,重置位置
    refresh本身并不复杂,但是对后面的影响比较大,算是初始化第一步的工作

    _initIndicator

    根据该方法会初始化一个滚动条,当然我们可以配置参数不让滚动条出现

     1 _initIndicator: function () {
     2   //滚动条
     3   var el = createDefaultScrollbar();
     4   this.wrapper.appendChild(el);
     5   this.indicator = new Indicator(this, { el: el });
     6 
     7   this.on('scrollEnd', function () {
     8     this.indicator.fade();
     9   });
    10 
    11   var scope = this;
    12   this.on('scrollCancel', function () {
    13     scope.indicator.fade();
    14   });
    15 
    16   this.on('scrollStart', function () {
    17     scope.indicator.fade(1);
    18   });
    19 
    20   this.on('beforeScrollStart', function () {
    21     scope.indicator.fade(1, true);
    22   });
    23 
    24   this.on('refresh', function () {
    25     scope.indicator.refresh();
    26   });
    27 
    28 },

    因为滚动条的类,我们后面会详细说,这里便不关注了

    _resize

    在我们窗口变化时候,或者竖着的屏幕横着时候便会触发该事件,他会执行我们的refresh方法,重置页面信息
    PS:其实就动画一块,IScroll逻辑是很严密的

    1 _resize: function () {
    2   var that = this;
    3 
    4   clearTimeout(this.resizeTimeout);
    5 
    6   this.resizeTimeout = setTimeout(function () {
    7     that.refresh();
    8   }, this.options.resizePolling);
    9 },

    resetPosition

     1 resetPosition: function (time) {
     2   var x = this.x,
     3 y = this.y;
     4 
     5   time = time || 0;
     6 
     7   if (this.y > 0) {
     8     y = 0;
     9   } else if (this.y < this.maxScrollY) {
    10     y = this.maxScrollY;
    11   }
    12 
    13   if (y == this.y) {
    14     return false;
    15   }
    16 
    17   this.scrollTo(x, y, time, this.options.bounceEasing);
    18 
    19   return true;
    20 },

    在refresh时候会触发(因为可能导致scroller超出边界),在touchend时候超出边界也会使用该方法,这个时候重置边界会有动画(600ms)

    动画结束如果检测到超出边界也会执行


    运动相关

    _transitionTime

     1 _transitionTime: function (time) {
     2   time = time || 0;
     3   this.scrollerStyle[utils.style.transitionDuration] = time + 'ms';
     4 
     5   //滚动条,我们这里只会出现一个滚动条就不搞那么复杂了
     6   this.indicator && this.indicator.transitionTime(time);
     7 
     8 },
     9 
    10 getComputedPosition: function () {
    11   var matrix = window.getComputedStyle(this.scroller, null), x, y;
    12 
    13   matrix = matrix[utils.style.transform].split(')')[0].split(', ');
    14   x = +(matrix[12] || matrix[4]);
    15   y = +(matrix[13] || matrix[5]);
    16 
    17   return { x: x, y: y };
    18 },

    该方法用于重置CSS3的时间参数属性,设置为0的话就不会具有动画,有一点需要注意的是,滚动条总是与他是同步的

    _transitionTimingFunction

    1 _transitionTimingFunction: function (easing) {
    2   this.scrollerStyle[utils.style.transitionTimingFunction] = easing;
    3 
    4   this.indicator && this.indicator.transitionTimingFunction(easing);
    5 },

    该方法用于重置CSS3的时间曲线,这个我没搞懂,滚动条同样是同步的

    _translate

     1 _translate: function (x, y) {
     2 
     3   this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ;
     4 
     5   this.x = x;
     6   this.y = y;
     7 
     8   if (this.options.scrollbars) {
     9     this.indicator.updatePosition();
    10   }
    11 
    12 },

    该方法会改变DOM的样式,但是其动画由CSS3的 transitionDuration 控制,这里改变了scroller样式会同步其滚动条的

    scrollTo

    该方法是以上三个方法的合集,除了_transitionTime会在外部经常出现以停止动画外,其它方法基本在该方法内部使用

     1 scrollTo: function (x, y, time, easing) {
     2   easing = easing || utils.ease.circular;
     3 
     4   this.isInTransition = time > 0;
     5 
     6   if (!time || easing.style) {
     7     this._transitionTimingFunction(easing.style);
     8     this._transitionTime(time);
     9     this._translate(x, y);
    10   }
    11 },

    因为滚动条的同步在各个子类方法中了,所以这个方法比较简单了,其逻辑进入了各个子方法

    如此整个继承的方法介绍结束,我们开始对三大核心事件进行解析

    三大事件核心

    说到三大核心就必须将isInTransition单独提出来,他只有0,1两个值,但是是记录DOM是否处于运动的关键

    start

     1 _start: function (e) {
     2   if (!this.enabled || (this.initiated && utils.eventType[e.type] !== this.initiated)) {
     3     return;
     4   }
     5 
     6   var point = e.touches ? e.touches[0] : e, pos;
     7   this.initiated = utils.eventType[e.type];
     8 
     9   this.moved = false;
    10 
    11   this.distY = 0;
    12 
    13   //开启动画时间,如果之前有动画的话,便要停止动画,这里因为没有传时间,所以动画便直接停止了
    14   this._transitionTime();
    15 
    16   this.startTime = utils.getTime();
    17 
    18   //如果正在进行动画,需要停止,并且触发滑动结束事件
    19   if (this.isInTransition) {
    20     this.isInTransition = false;
    21     pos = this.getComputedPosition();
    22 
    23     //移动过去
    24     this._translate(Math.round(pos.x), Math.round(pos.y));
    25     this._execEvent('scrollEnd');
    26   }
    27 
    28   this.startX = this.x;
    29   this.startY = this.y;
    30   this.absStartX = this.x;
    31   this.absStartY = this.y;
    32   this.pointX = point.pageX;
    33   this.pointY = point.pageY;
    34 
    35   this._execEvent('beforeScrollStart');
    36 
    37   e.preventDefault();
    38 
    39 },

    在开始阶段,首先将moved属性设置为false,然后停止动画,如果当前isInTransition值为1,需要将他设置为false并且保持当前的状态

    PS:这一步是停止动画并且保持当前状态的关键,这里会触发scrollEnd事件,滚动条会有所联动

    在做一些初始化操作后,该方法结束,这个里面的核心就是停止动画并且做初始化操作

    move

    该阶段是第二核心,这里会根据移动获得新的坐标开始移动,如果超出边界会做一个处理,移动的值不会超过1/3个MaxY
    一个关键点是会触发scrollStart事件,让滚动条可见
    另一个关键点是没过300ms会重置开始状态值,所以就算使劲按着屏幕不放,突然放开,DOM仍然会移动很远

    end

     1 _end: function (e) {
     2   if (!this.enabled || utils.eventType[e.type] !== this.initiated) {
     3     return;
     4   }
     5 
     6   var point = e.changedTouches ? e.changedTouches[0] : e,
     7   momentumY,
     8   duration = utils.getTime() - this.startTime,
     9   newX = Math.round(this.x),
    10   newY = Math.round(this.y),
    11   distanceX = Math.abs(newX - this.startX),
    12   distanceY = Math.abs(newY - this.startY),
    13   time = 0,
    14   easing = '';
    15 
    16   this.isInTransition = 0;
    17   this.initiated = 0;
    18   this.endTime = utils.getTime();
    19 
    20   if (this.resetPosition(this.options.bounceTime)) {
    21     return;
    22   }
    23 
    24   this.scrollTo(newX, newY);
    25   if (!this.moved) {
    26     //click 的情况
    27 
    28     this._execEvent('scrollCancel');
    29     return;
    30   }
    31 
    32   if (duration < 300) {
    33 
    34     momentumY = utils.momentum(this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0);
    35     //      newX = momentumX.destination;
    36     newY = momentumY.destination;
    37     time = Math.max(momentumY.duration);
    38     this.isInTransition = 1;
    39   }
    40 
    41   if (newY != this.y) {
    42     if (newY > 0 || newY < this.maxScrollY) {
    43       easing = utils.ease.quadratic;
    44     }
    45     this.scrollTo(newX, newY, time, easing);
    46     return;
    47   }
    48 
    49   this._execEvent('scrollEnd');
    50 },
    View Code

    end阶段首先会将isInTransition设置为0,如果超出边界会进行处理,进行动画又会将 isInTransition 设置为1,在动画事件结束后设置为0

    期间还会处理一些其他情况,我们不予理睬,重点就是使用momentum获取了当前运动参数
    然后开始运动,此处逻辑全部分解开了,这里反而显得不难了

    至此,整个核心块IScroll便分解结束了,我们最后看看滚动条相关

    Indicator

    因为我们对滚动条的需求变动简单,我们的滚动条现在就真的很简单了,完全与scroller是一个从属关系,随着scroller而变化
    有兴趣的朋友自己去看看吧,我这里就不管他了

    源码:

      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4 <meta charset="utf-8">
      5 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">
      6 
      7 <title>iScroll demo: scrollbars</title>
      8 
      9 
     10 <style type="text/css">
     11 * {
     12     -webkit-box-sizing: border-box;
     13     -moz-box-sizing: border-box;
     14     box-sizing: border-box;
     15 }
     16 
     17 html {
     18     -ms-touch-action: none;
     19 }
     20 
     21 body,ul,li {
     22     padding: 0;
     23     margin: 0;
     24     border: 0;
     25 }
     26 
     27 body {
     28     font-size: 12px;
     29     font-family: ubuntu, helvetica, arial;
     30     overflow: hidden; /* this is important to prevent the whole page to bounce */
     31 }
     32 
     33 #header {
     34     position: absolute;
     35     z-index: 2;
     36     top: 0;
     37     left: 0;
     38     width: 100%;
     39     height: 45px;
     40     line-height: 45px;
     41     background: #CD235C;
     42     padding: 0;
     43     color: #eee;
     44     font-size: 20px;
     45     text-align: center;
     46     font-weight: bold;
     47 }
     48 
     49 #footer {
     50     position: absolute;
     51     z-index: 2;
     52     bottom: 0;
     53     left: 0;
     54     width: 100%;
     55     height: 48px;
     56     background: #444;
     57     padding: 0;
     58     border-top: 1px solid #444;
     59 }
     60 
     61 #wrapper {
     62     position: absolute;
     63     z-index: 1;
     64     top: 45px;
     65     bottom: 48px;
     66     left: 0;
     67     width: 100%;
     68     background: #ccc;
     69     overflow: hidden;
     70 }
     71 
     72 #scroller {
     73     position: absolute;
     74     z-index: 1;
     75     -webkit-tap-highlight-color: rgba(0,0,0,0);
     76     width: 100%;
     77     -webkit-transform: translateZ(0);
     78     -moz-transform: translateZ(0);
     79     -ms-transform: translateZ(0);
     80     -o-transform: translateZ(0);
     81     transform: translateZ(0);
     82     -webkit-touch-callout: none;
     83     -webkit-user-select: none;
     84     -moz-user-select: none;
     85     -ms-user-select: none;
     86     user-select: none;
     87     -webkit-text-size-adjust: none;
     88     -moz-text-size-adjust: none;
     89     -ms-text-size-adjust: none;
     90     -o-text-size-adjust: none;
     91     text-size-adjust: none;
     92 }
     93 
     94 #scroller ul {
     95     list-style: none;
     96     padding: 0;
     97     margin: 0;
     98     width: 100%;
     99     text-align: left;
    100 }
    101 
    102 #scroller li {
    103     padding: 0 10px;
    104     height: 40px;
    105     line-height: 40px;
    106     border-bottom: 1px solid #ccc;
    107     border-top: 1px solid #fff;
    108     background-color: #fafafa;
    109     font-size: 14px;
    110 }
    111 
    112 </style>
    113 </head>
    114 <body  >
    115 <div id="header">抄袭IScroll</div>
    116 
    117 <div id="wrapper">
    118     <div id="scroller">
    119         <ul>
    120             <li>Pretty row 1</li>
    121             <li>Pretty row 2</li>
    122             <li>Pretty row 3</li>
    123             <li>Pretty row 4</li>
    124             <li>Pretty row 5</li>
    125             <li>Pretty row 6</li>
    126             <li>Pretty row 7</li>
    127             <li>Pretty row 8</li>
    128             <li>Pretty row 9</li>
    129             <li>Pretty row 10</li>
    130             <li>Pretty row 11</li>
    131             <li>Pretty row 12</li>
    132             <li>Pretty row 13</li>
    133             <li>Pretty row 14</li>
    134             <li>Pretty row 15</li>
    135             <li>Pretty row 16</li>
    136             <li>Pretty row 17</li>
    137             <li>Pretty row 18</li>
    138             <li>Pretty row 19</li>
    139             <li>Pretty row 20</li>
    140             <li>Pretty row 21</li>
    141             <li>Pretty row 22</li>
    142             <li>Pretty row 23</li>
    143             <li>Pretty row 24</li>
    144             <li>Pretty row 25</li>
    145             <li>Pretty row 26</li>
    146             <li>Pretty row 27</li>
    147             <li>Pretty row 28</li>
    148             <li>Pretty row 29</li>
    149             <li>Pretty row 30</li>
    150             <li>Pretty row 31</li>
    151             <li>Pretty row 32</li>
    152             <li>Pretty row 33</li>
    153             <li>Pretty row 34</li>
    154             <li>Pretty row 35</li>
    155             <li>Pretty row 36</li>
    156             <li>Pretty row 37</li>
    157             <li>Pretty row 38</li>
    158             <li>Pretty row 39</li>
    159             <li>Pretty row 40</li>
    160             <li>Pretty row 41</li>
    161             <li>Pretty row 42</li>
    162             <li>Pretty row 43</li>
    163             <li>Pretty row 44</li>
    164             <li>Pretty row 45</li>
    165             <li>Pretty row 46</li>
    166             <li>Pretty row 47</li>
    167             <li>Pretty row 48</li>
    168             <li>Pretty row 49</li>
    169             <li>Pretty row 50</li>
    170         </ul>
    171     </div>
    172 </div>
    173 
    174 <div id="footer"></div>
    175 
    176   <script src="zepto.js" type="text/javascript"></script>
    177   <script src="MyIscroll.js" type="text/javascript"></script>
    178 
    179   <script type="text/javascript">
    180 
    181     var s = new IScroll({
    182       wrapper: $('#wrapper'),
    183       scroller: $('#scroller')
    184     });
    185 
    186   </script>
    187 </body>
    188 </html>
    View Code
      1 (function (window, document, Math) {
      2 
      3   var utils = (function () {
      4     var me = {};
      5     var _elementStyle = document.createElement('div').style;
      6 
      7     //获得需要兼容CSS3前缀
      8     var _vendor = (function () {
      9       var vendors = ['t', 'webkitT', 'MozT', 'msT', 'OT'];
     10       var transform;
     11       var i = 0;
     12       var l = vendors.length;
     13 
     14       for (; i < l; i++) {
     15         transform = vendors[i] + 'ransform';
     16         if (transform in _elementStyle) return vendors[i].substr(0, vendors[i].length - 1);
     17       }
     18       return false;
     19     })();
     20 
     21     //获取样式(CSS3兼容)
     22     function _prefixStyle(style) {
     23       if (_vendor === false) return false;
     24       if (_vendor === '') return style;
     25       return _vendor + style.charAt(0).toUpperCase() + style.substr(1);
     26     }
     27 
     28     me.getTime = Date.now || function getTime() { return new Date().getTime(); };
     29 
     30     me.addEvent = function (el, type, fn, capture) {
     31       if (el[0]) el = el[0];
     32       el.addEventListener(type, fn, !!capture);
     33     };
     34 
     35     me.removeEvent = function (el, type, fn, capture) {
     36       if (el[0]) el = el[0];
     37       el.removeEventListener(type, fn, !!capture);
     38     };
     39 
     40     /*
     41     current:当前鼠标位置
     42     start:touchStart时候记录的Y(可能是X)的开始位置,但是在touchmove时候可能被重写
     43     time: touchstart到手指离开时候经历的时间,同样可能被touchmove重写
     44     lowerMargin:y可移动的最大距离,这个一般为计算得出 this.wrapperHeight - this.scrollerHeight
     45     wrapperSize:如果有边界距离的话就是可拖动,不然碰到0的时候便停止
     46     */
     47     me.momentum = function (current, start, time, lowerMargin, wrapperSize) {
     48       var distance = current - start,
     49         speed = Math.abs(distance) / time,
     50         destination,
     51         duration,
     52         deceleration = 0.0006;
     53 
     54       destination = current + (speed * speed) / (2 * deceleration) * (distance < 0 ? -1 : 1);
     55       duration = speed / deceleration;
     56 
     57       if (destination < lowerMargin) {
     58         destination = wrapperSize ? lowerMargin - (wrapperSize / 2.5 * (speed / 8)) : lowerMargin;
     59         distance = Math.abs(destination - current);
     60         duration = distance / speed;
     61       } else if (destination > 0) {
     62         destination = wrapperSize ? wrapperSize / 2.5 * (speed / 8) : 0;
     63         distance = Math.abs(current) + destination;
     64         duration = distance / speed;
     65       }
     66 
     67       return {
     68         destination: Math.round(destination),
     69         duration: duration
     70       };
     71     };
     72 
     73     $.extend(me, {
     74       hasTouch: 'ontouchstart' in window
     75     });
     76 
     77 
     78     //我们暂时只判断touch 和 mouse即可
     79     $.extend(me.style = {}, {
     80       transform: _prefixStyle('transform'),
     81       transitionTimingFunction: _prefixStyle('transitionTimingFunction'),
     82       transitionDuration: _prefixStyle('transitionDuration'),
     83       transitionDelay: _prefixStyle('transitionDelay'),
     84       transformOrigin: _prefixStyle('transformOrigin')
     85     });
     86 
     87     $.extend(me.eventType = {}, {
     88       touchstart: 1,
     89       touchmove: 1,
     90       touchend: 1,
     91 
     92       mousedown: 2,
     93       mousemove: 2,
     94       mouseup: 2
     95     });
     96 
     97     $.extend(me.ease = {}, {
     98       quadratic: {
     99         style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
    100         fn: function (k) {
    101           return k * (2 - k);
    102         }
    103       },
    104       circular: {
    105         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)
    106         fn: function (k) {
    107           return Math.sqrt(1 - (--k * k));
    108         }
    109       },
    110       back: {
    111         style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
    112         fn: function (k) {
    113           var b = 4;
    114           return (k = k - 1) * k * ((b + 1) * k + b) + 1;
    115         }
    116       },
    117       bounce: {
    118         style: '',
    119         fn: function (k) {
    120           if ((k /= 1) < (1 / 2.75)) {
    121             return 7.5625 * k * k;
    122           } else if (k < (2 / 2.75)) {
    123             return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75;
    124           } else if (k < (2.5 / 2.75)) {
    125             return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375;
    126           } else {
    127             return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375;
    128           }
    129         }
    130       },
    131       elastic: {
    132         style: '',
    133         fn: function (k) {
    134           var f = 0.22,
    135         e = 0.4;
    136 
    137           if (k === 0) { return 0; }
    138           if (k == 1) { return 1; }
    139 
    140           return (e * Math.pow(2, -10 * k) * Math.sin((k - f / 4) * (2 * Math.PI) / f) + 1);
    141         }
    142       }
    143     });
    144     return me;
    145   })();
    146 
    147   function IScroll(opts) {
    148     this.wrapper = typeof opts.wrapper == 'string' ? $(opts.wrapper) : opts.wrapper;
    149     this.scroller = typeof opts.scroller == 'string' ? $(opts.scroller) : opts.scroller;
    150     if (!opts.wrapper[0] || !opts.scroller[0]) throw 'param error';
    151 
    152     this.wrapper = this.wrapper[0];
    153     this.scroller = this.scroller[0];
    154 
    155     //这个属性会被动态改变的,如果这里
    156     this.scrollerStyle = this.scroller.style;
    157 
    158     this.options = {
    159       //是否具有滚动条
    160       scrollbars: true,
    161       // 其实时期Y的位置
    162       startY: 0,
    163       //超出边界还原时间点
    164       bounceTime: 600,
    165       //超出边界返回的动画
    166       bounceEasing: utils.ease.circular,
    167 
    168       //超出边界时候是否还能拖动
    169       bounce: true,
    170 
    171       //当window触发resize事件60ms后还原
    172       resizePolling: 60,
    173       startX: 0,
    174       startY: 0
    175     };
    176 
    177     for (var i in opts) {
    178       this.options[i] = opts[i];
    179     }
    180 
    181     this.translateZ = ' translateZ(0)';
    182 
    183     this.x = 0;
    184     this.y = 0;
    185     this._events = {};
    186     this._init();
    187 
    188     //更新滚动条位置
    189     this.refresh();
    190 
    191     //更新本身位置
    192     this.scrollTo(this.options.startX, this.options.startY);
    193 
    194     this.enable();
    195 
    196   };
    197 
    198   IScroll.prototype = {
    199     _init: function () {
    200       this._initEvents();
    201 
    202       //初始化滚动条,滚动条此处需要做重要处理
    203       if (this.options.scrollbars) {
    204         this._initIndicator();
    205       }
    206     },
    207     refresh: function () {
    208       var rf = this.wrapper.offsetHeight;     // Force reflow
    209 
    210       this.wrapperHeight = this.wrapper.clientHeight;
    211       this.scrollerHeight = this.scroller.offsetHeight;
    212       this.maxScrollY = this.wrapperHeight - this.scrollerHeight;
    213 
    214       this.endTime = 0;
    215 
    216       this._execEvent('refresh');
    217 
    218       this.resetPosition();
    219 
    220     },
    221     _initEvents: function (remove) {
    222       var eventType = remove ? utils.removeEvent : utils.addEvent;
    223       var target = this.options.bindToWrapper ? this.wrapper : window;
    224 
    225       eventType(window, 'orientationchange', this);
    226       eventType(window, 'resize', this);
    227 
    228       if (utils.hasTouch) {
    229         eventType(this.wrapper, 'touchstart', this);
    230         eventType(target, 'touchmove', this);
    231         eventType(target, 'touchcancel', this);
    232         eventType(target, 'touchend', this);
    233       } else {
    234         eventType(this.wrapper, 'mousedown', this);
    235         eventType(target, 'mousemove', this);
    236         eventType(target, 'mousecancel', this);
    237         eventType(target, 'mouseup', this);
    238       }
    239 
    240       eventType(this.scroller, 'transitionend', this);
    241       eventType(this.scroller, 'webkitTransitionEnd', this);
    242       eventType(this.scroller, 'oTransitionEnd', this);
    243       eventType(this.scroller, 'MSTransitionEnd', this);
    244     },
    245     _start: function (e) {
    246       if (!this.enabled || (this.initiated && utils.eventType[e.type] !== this.initiated)) {
    247         return;
    248       }
    249 
    250       var point = e.touches ? e.touches[0] : e, pos;
    251       this.initiated = utils.eventType[e.type];
    252 
    253       this.moved = false;
    254 
    255       this.distY = 0;
    256 
    257       //开启动画时间,如果之前有动画的话,便要停止动画,这里因为没有传时间,所以动画便直接停止了
    258       this._transitionTime();
    259 
    260       this.startTime = utils.getTime();
    261 
    262       //如果正在进行动画,需要停止,并且触发滑动结束事件
    263       if (this.isInTransition) {
    264         this.isInTransition = false;
    265         pos = this.getComputedPosition();
    266 
    267         //移动过去
    268         this._translate(Math.round(pos.x), Math.round(pos.y));
    269         this._execEvent('scrollEnd');
    270       }
    271 
    272       this.startX = this.x;
    273       this.startY = this.y;
    274       this.absStartX = this.x;
    275       this.absStartY = this.y;
    276       this.pointX = point.pageX;
    277       this.pointY = point.pageY;
    278 
    279       this._execEvent('beforeScrollStart');
    280 
    281       e.preventDefault();
    282 
    283     },
    284 
    285     _move: function (e) {
    286       if (!this.enabled || utils.eventType[e.type] !== this.initiated) {
    287         return;
    288       }
    289 
    290       e.preventDefault();
    291 
    292       var point = e.touches ? e.touches[0] : e,
    293       deltaX = point.pageX - this.pointX,
    294       deltaY = point.pageY - this.pointY,
    295       timestamp = utils.getTime(),
    296       newX, newY,
    297       absDistX, absDistY;
    298 
    299       this.pointX = point.pageX;
    300       this.pointY = point.pageY;
    301 
    302       this.distX += deltaX;
    303       this.distY += deltaY;
    304       absDistX = Math.abs(this.distX);
    305       absDistY = Math.abs(this.distY);
    306 
    307       // 如果一直按着没反应的话这里就直接返回了
    308       if (timestamp - this.endTime > 300 && (absDistX < 10 && absDistY < 10)) {
    309         return;
    310       }
    311 
    312       newY = this.y + deltaY;
    313 
    314       if (newY > 0 || newY < this.maxScrollY) {
    315         newY = this.options.bounce ? this.y + deltaY / 3 : newY > 0 ? 0 : this.maxScrollY;
    316       }
    317 
    318       if (!this.moved) {
    319         this._execEvent('scrollStart');
    320       }
    321 
    322       this.moved = true;
    323 
    324       this._translate(0, newY);
    325 
    326       if (timestamp - this.startTime > 300) {
    327         this.startTime = timestamp;
    328         this.startX = this.x;
    329         this.startY = this.y;
    330       }
    331 
    332 
    333     },
    334     _end: function (e) {
    335       if (!this.enabled || utils.eventType[e.type] !== this.initiated) {
    336         return;
    337       }
    338 
    339       var point = e.changedTouches ? e.changedTouches[0] : e,
    340       momentumY,
    341       duration = utils.getTime() - this.startTime,
    342       newX = Math.round(this.x),
    343       newY = Math.round(this.y),
    344       distanceX = Math.abs(newX - this.startX),
    345       distanceY = Math.abs(newY - this.startY),
    346       time = 0,
    347       easing = '';
    348 
    349       this.isInTransition = 0;
    350       this.initiated = 0;
    351       this.endTime = utils.getTime();
    352 
    353       if (this.resetPosition(this.options.bounceTime)) {
    354         return;
    355       }
    356 
    357       this.scrollTo(newX, newY);
    358       if (!this.moved) {
    359         //click 的情况
    360 
    361         this._execEvent('scrollCancel');
    362         return;
    363       }
    364 
    365       if (duration < 300) {
    366 
    367         momentumY = utils.momentum(this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0);
    368         //      newX = momentumX.destination;
    369         newY = momentumY.destination;
    370         time = Math.max(momentumY.duration);
    371         this.isInTransition = 1;
    372       }
    373 
    374       if (newY != this.y) {
    375         if (newY > 0 || newY < this.maxScrollY) {
    376           easing = utils.ease.quadratic;
    377         }
    378         this.scrollTo(newX, newY, time, easing);
    379         return;
    380       }
    381 
    382       this._execEvent('scrollEnd');
    383     },
    384 
    385     _resize: function () {
    386       var that = this;
    387 
    388       clearTimeout(this.resizeTimeout);
    389 
    390       this.resizeTimeout = setTimeout(function () {
    391         that.refresh();
    392       }, this.options.resizePolling);
    393     },
    394 
    395     _transitionTimingFunction: function (easing) {
    396       this.scrollerStyle[utils.style.transitionTimingFunction] = easing;
    397 
    398       this.indicator && this.indicator.transitionTimingFunction(easing);
    399     },
    400 
    401     //开始或者停止动画
    402     _transitionTime: function (time) {
    403       time = time || 0;
    404       this.scrollerStyle[utils.style.transitionDuration] = time + 'ms';
    405 
    406       //滚动条,我们这里只会出现一个滚动条就不搞那么复杂了
    407       this.indicator && this.indicator.transitionTime(time);
    408 
    409     },
    410 
    411     getComputedPosition: function () {
    412       var matrix = window.getComputedStyle(this.scroller, null), x, y;
    413 
    414       matrix = matrix[utils.style.transform].split(')')[0].split(', ');
    415       x = +(matrix[12] || matrix[4]);
    416       y = +(matrix[13] || matrix[5]);
    417 
    418       return { x: x, y: y };
    419     },
    420 
    421     _initIndicator: function () {
    422       //滚动条
    423       var el = createDefaultScrollbar();
    424       this.wrapper.appendChild(el);
    425       this.indicator = new Indicator(this, { el: el });
    426 
    427       this.on('scrollEnd', function () {
    428         this.indicator.fade();
    429       });
    430 
    431       var scope = this;
    432       this.on('scrollCancel', function () {
    433         scope.indicator.fade();
    434       });
    435 
    436       this.on('scrollStart', function () {
    437         scope.indicator.fade(1);
    438       });
    439 
    440       this.on('beforeScrollStart', function () {
    441         scope.indicator.fade(1, true);
    442       });
    443 
    444       this.on('refresh', function () {
    445         scope.indicator.refresh();
    446       });
    447 
    448     },
    449 
    450     //移动x,y这里比较简单就不分离y了
    451     _translate: function (x, y) {
    452 
    453       this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ;
    454 
    455       this.x = x;
    456       this.y = y;
    457 
    458       if (this.options.scrollbars) {
    459         this.indicator.updatePosition();
    460       }
    461 
    462     },
    463 
    464     resetPosition: function (time) {
    465       var x = this.x,
    466         y = this.y;
    467 
    468       time = time || 0;
    469 
    470       if (this.y > 0) {
    471         y = 0;
    472       } else if (this.y < this.maxScrollY) {
    473         y = this.maxScrollY;
    474       }
    475 
    476       if (y == this.y) {
    477         return false;
    478       }
    479 
    480       this.scrollTo(x, y, time, this.options.bounceEasing);
    481 
    482       return true;
    483     },
    484 
    485     //移动
    486     scrollTo: function (x, y, time, easing) {
    487       easing = easing || utils.ease.circular;
    488 
    489       this.isInTransition = time > 0;
    490 
    491       if (!time || easing.style) {
    492         this._transitionTimingFunction(easing.style);
    493         this._transitionTime(time);
    494         this._translate(x, y);
    495       }
    496     },
    497 
    498     //统一的关闭接口
    499     disable: function () {
    500       this.enabled = false;
    501     },
    502     //统一的open接口
    503     enable: function () {
    504       this.enabled = true;
    505     },
    506 
    507     on: function (type, fn) {
    508       if (!this._events[type]) {
    509         this._events[type] = [];
    510       }
    511 
    512       this._events[type].push(fn);
    513     },
    514 
    515     _execEvent: function (type) {
    516       if (!this._events[type]) {
    517         return;
    518       }
    519 
    520       var i = 0,
    521             l = this._events[type].length;
    522 
    523       if (!l) {
    524         return;
    525       }
    526 
    527       for (; i < l; i++) {
    528         this._events[type][i].call(this);
    529       }
    530     },
    531     destroy: function () {
    532       this._initEvents(true);
    533 
    534       this._execEvent('destroy');
    535     },
    536 
    537     _transitionEnd: function (e) {
    538       if (e.target != this.scroller || !this.isInTransition) {
    539         return;
    540       }
    541 
    542       this._transitionTime();
    543       if (!this.resetPosition(this.options.bounceTime)) {
    544         this.isInTransition = false;
    545         this._execEvent('scrollEnd');
    546       }
    547     },
    548 
    549     //事件具体触发点
    550     handleEvent: function (e) {
    551       switch (e.type) {
    552         case 'touchstart':
    553         case 'mousedown':
    554           this._start(e);
    555           break;
    556         case 'touchmove':
    557         case 'mousemove':
    558           this._move(e);
    559           break;
    560         case 'touchend':
    561         case 'mouseup':
    562         case 'touchcancel':
    563         case 'mousecancel':
    564           this._end(e);
    565           break;
    566         case 'orientationchange':
    567         case 'resize':
    568           this._resize();
    569           break;
    570         case 'transitionend':
    571         case 'webkitTransitionEnd':
    572         case 'oTransitionEnd':
    573         case 'MSTransitionEnd':
    574           this._transitionEnd(e);
    575           break;
    576       }
    577     }
    578 
    579   };
    580 
    581   function createDefaultScrollbar() {
    582     var scrollbar = document.createElement('div'),
    583         indicator = document.createElement('div');
    584 
    585     scrollbar.style.cssText = 'position:absolute;z-index:9999';
    586     scrollbar.style.cssText += ';7px;bottom:2px;top:2px;right:1px';
    587     scrollbar.style.cssText += ';overflow:hidden';
    588 
    589     indicator.style.cssText = '-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;position:absolute;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);border-radius:3px';
    590     indicator.style.width = '100%';
    591 
    592     scrollbar.appendChild(indicator);
    593 
    594     return scrollbar;
    595   }
    596 
    597   function Indicator(scroller, opts) {
    598     this.wrapper = typeof opts.el == 'string' ? document.querySelector(opts.el) : opts.el;
    599     this.indicator = this.wrapper.children[0];
    600 
    601     this.wrapperStyle = this.wrapper.style;
    602     this.indicatorStyle = this.indicator.style;
    603     this.scroller = scroller;
    604 
    605     this.sizeRatioY = 1;
    606     this.maxPosY = 0;
    607 
    608     this.wrapperStyle[utils.style.transform] = this.scroller.translateZ;
    609     this.wrapperStyle[utils.style.transitionDuration] = '0ms';
    610     this.wrapperStyle.opacity = '0';
    611 
    612   }
    613 
    614   Indicator.prototype = {
    615     transitionTime: function (time) {
    616       time = time || 0;
    617       this.indicatorStyle[utils.style.transitionDuration] = time + 'ms';
    618     },
    619     transitionTimingFunction: function (easing) {
    620       this.indicatorStyle[utils.style.transitionTimingFunction] = easing;
    621     },
    622     refresh: function () {
    623 
    624       this.transitionTime();
    625 
    626       var r = this.wrapper.offsetHeight; // force refresh
    627 
    628       this.wrapperHeight = this.wrapper.clientHeight;
    629 
    630 
    631       this.indicatorHeight = Math.max(Math.round(this.wrapperHeight * this.wrapperHeight / (this.scroller.scrollerHeight || this.wrapperHeight || 1)), 8);
    632       this.indicatorStyle.height = this.indicatorHeight + 'px';
    633 
    634 
    635       this.maxPosY = this.wrapperHeight - this.indicatorHeight;
    636       this.sizeRatioY = (this.scroller.maxScrollY && (this.maxPosY / this.scroller.maxScrollY));
    637 
    638       this.updatePosition();
    639     },
    640     updatePosition: function () {
    641       var y = Math.round(this.sizeRatioY * this.scroller.y) || 0;
    642       this.y = y;
    643 
    644       //不需要兼容方式了
    645       this.indicatorStyle[utils.style.transform] = 'translate(0px,' + y + 'px)' + this.scroller.translateZ;
    646 
    647     },
    648     fade: function (val, hold) {
    649       if (hold && !this.visible) {
    650         return;
    651       }
    652 
    653       clearTimeout(this.fadeTimeout);
    654       this.fadeTimeout = null;
    655 
    656       var time = val ? 250 : 500,
    657             delay = val ? 0 : 300;
    658 
    659       val = val ? '1' : '0';
    660 
    661       this.wrapperStyle[utils.style.transitionDuration] = time + 'ms';
    662 
    663       this.fadeTimeout = setTimeout((function (val) {
    664         this.wrapperStyle.opacity = val;
    665         this.visible = +val;
    666       }).bind(this, val), delay);
    667 
    668     }
    669   };
    670 
    671   IScroll.utils = utils;
    672 
    673 
    674   window.IScroll = IScroll;
    675 
    676 
    677 })(window, document, Math);
    View Code

    http://sandbox.runjs.cn/show/naryr1vy

    结语

    我们这次抄袭了IScroll核心代码,形成了一个简单的拖动库,我们对IScroll的学习可能暂时就到这里了,接下来一段时间我们的重心会放到nodeJS上面
    中途可能会涉及到requireJS的源码学习,但是都不会偏离nodeJS的核心

    注意,请看最后的代码......

  • 相关阅读:
    4、自定义菜单
    3、关注、取消关注 与 关键字回复
    2、自动回复消息
    1、接入公众平台
    java学习备忘录
    vue组件最佳实践
    js拉起或下载app
    angular1.5 Components
    Charlse 使用小记
    2016年终总结
  • 原文地址:https://www.cnblogs.com/yexiaochai/p/3505929.html
Copyright © 2020-2023  润新知