zoukankan      html  css  js  c++  java
  • 【iScroll源码学习04】分离IScroll核心

    【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的时候便停止

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

    属性检测

    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

    View Code

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

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

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

    Indicator

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

    源码:

    View Code
    View Code

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

    结语

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

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

  • 相关阅读:
    Asp.Net Core MVC + Code First + Mysql 项目创建以及相关配置
    linux安装 docker compose v2
    压缩、解压 解决 客户端查询大批量数据时等待时间过长的问题
    c# 通过经纬度 查询地址、区域信息
    excel 文件转 dataset ,jqgrid 中 模糊查询与下拉联动的实现
    jqgrid mvc 导出excel
    SQL学习笔记三表的字段操作
    SQL学习笔记高级教程
    安装docker
    SQL学习笔记一数据类型
  • 原文地址:https://www.cnblogs.com/xuqiudong/p/4773224.html
Copyright © 2011-2022 走看看