博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【iScroll源码学习04】分离IScroll核心
阅读量:6080 次
发布时间:2019-06-20

本文共 10842 字,大约阅读时间需要 36 分钟。

前言

最近几天我们前前后后基本将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.scrollerHeightwrapperSize:如果有边界距离的话就是可拖动,不然碰到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

结语

我们这次抄袭了IScroll核心代码,形成了一个简单的拖动库,我们对IScroll的学习可能暂时就到这里了,接下来一段时间我们的重心会放到nodeJS上面

中途可能会涉及到requireJS的源码学习,但是都不会偏离nodeJS的核心

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

转载于:https://www.cnblogs.com/widow/p/4773224.html

你可能感兴趣的文章
UE4 Class Naming Prefixes
查看>>
30个优秀的网站导航菜单设计案例
查看>>
Eclipse Tips(3):Template
查看>>
Win32 API消息函数:GetMessagePos
查看>>
跟小静读CLR via C#(17)--接口
查看>>
C#WinForm应用程序实现自动填充网页上的用户名和密码并点击登录按钮
查看>>
[Z]一个轻松制作和处理矢量图的工具和方法
查看>>
PetaPoco的默认映射
查看>>
POJ 基本算法(3)
查看>>
最小生成树两个重要的算法:Prim 和 Kruskal
查看>>
SQL SERVER 中常见的高可用方案
查看>>
PHP 反射 初步测试
查看>>
安装MySQLdb-python时无法找到-lprobes_mysql处理一则
查看>>
对计算机模拟人脑的一个小想法
查看>>
CI分页器pagination的原理及实现
查看>>
The Rox Java NIO Tutorial
查看>>
如何选择婴幼儿奶粉?
查看>>
MySQL global Log
查看>>
BZOJ3564 : [SHOI2014]信号增幅仪
查看>>
发布流程考虑
查看>>