【iScroll源码学习02】分解iScroll三个核心事件点
2014-01-01 15:50
337 查看
前言
最近两天看到很多的总结性发言,我想想今年好像我的变化挺大的,是不是该晚上来水一发呢?嗯,决定了,晚上来水一发!上周六,我们简单模拟了下iScroll的实现,周日我们开始了学习iScroll的源码,今天我们接着上次的记录学习,因为最近事情稍微有点多了
学习进度可能要放慢,而且iScroll这个库实际意义很大,不能囫囵吞枣的学习,要学到精华,并且要用于项目中的,所以初步规划是最近两周主要围绕iScroll展开
而后两个选择:① 分离iScroll代码用于项目;② 抄袭iScroll精华部分用于项目。无论如何都要用于项目......
iScroll源码学习01准备阶段
SPA移动站点APP化研究之上中下页面的iScroll化
几个事件点
iScroll的整体逻辑由三大事件点组成:① touchStart(mousedown)
② touchMove(mousemove)
③ touchEnd(mouseUp)
也就是iScroll整体的功能逻辑其实是由这几个事件串起来的,其中
touchStart会保留一些初始化操作,或者停止正在进行的动画
touchMove会带动dom一起移动
而touchEnd最为复杂,在touchend阶段可能需要处理很多东西
① 一般性拖动结束事件 ② 超出边界还原后触发的事件(此时可以滚动加载数据) ③ 如果此次为一次按钮点击,需要触发按钮事件那么还有对preventDefault进行处理(preventDefault可能导致事件不触发) ④ 如果此次为一次点击事件,并且对象为文本框或者select(其它会获得焦点的事件),那么应该让其获得焦点,并且弹出键盘 ⑤ ......
以上为主观臆测下的猜想,我们来看看iScroll实际干了些什么,下面再细细的分析各个阶段
start
_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), 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 if (this.options.momentum && duration < 300) { momentumX = this.hasHorizontalScroll ? utils.momentum(this.x, this.startX, duration, this.maxScrollX, this.options.bounce ? this.wrapperWidth : 0) : { destination: newX, duration: 0 }; momentumY = this.hasVerticalScroll ? utils.momentum(this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0) : { destination: newY, duration: 0 }; newX = momentumX.destination; newY = momentumY.destination; time = Math.max(momentumX.duration, momentumY.duration); this.isInTransition = 1; } if (this.options.snap) { var snap = this._nearestSnap(newX, newY); this.currentPage = snap; time = this.options.snapSpeed || Math.max( Math.max( Math.min(Math.abs(newX - snap.x), 1000), Math.min(Math.abs(newY - snap.y), 1000) ), 300); newX = snap.x; newY = snap.y; this.directionX = 0; this.directionY = 0; easing = this.options.bounceEasing; } // 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'); },
View Code
开始我们就说过,touchend为这个控件一个关键点与难点,现在我们就来啃一啃,这个看完了,iScroll核心部分也就结束了,后面就只需要拆解分析即可
首先仍然是一点初始化操作
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), distanceY = Math.abs(newY - this.startY), time = 0, easing = ''; this.isInTransition = 0; this.initiated = 0; this.endTime = utils.getTime();
duration是当前拖动的事件,这里可不是手指触屏到离开哦,因为move时候每300ms变了一次
PS:这里想象一下如果我们想要快速的滑动是不是触屏屏幕很快呢,而我们一直拖动DOM在最后也是有可能想让他快速移动的
记录当前x,y位置记录当前移动位置distanceY,然后重置结束时间,这里有一个resetPosition方法:
resetPosition: function (time) { var x = this.x, y = this.y; time = time || 0; if ( !this.hasHorizontalScroll || this.x > 0 ) { x = 0; } else if ( this.x < this.maxScrollX ) { x = this.maxScrollX; } if ( !this.hasVerticalScroll || this.y > 0 ) { y = 0; } else if ( this.y < this.maxScrollY ) { y = this.maxScrollY; } if ( x == this.x && y == this.y ) { return false; } this.scrollTo(x, y, time, this.options.bounceEasing); return true; },
他是记录我们是不是已经离开了边界了,如果离开边界了就不会执行后面逻辑,而直接重置DOM位置,这里还用到了我们的scrollTo方法,该方法尤其关键
scrollTo
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); } },
这个方法是此处一个重要的方法,传入距离与时间后,他就会高高兴兴的移动到对应位置
如果启用了CSS3的动画,便会使用CSS3动画方式进行动画(这个动画我们下期再说),否则使用_animate方法(js实现方案)
_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(); },
这里用到了前文说描述的settimeout实现动画方案,这里有一点需要我们回到start部分重新思考,为什么CSS停止了动画?
原因是因为transitionend事件
transitionend 事件会在 CSS transition 结束后触发. 当transition完成前移除transition时,比如移除css的transition-property 属性,事件将不会被触发.
_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'); } },
所以,我们第二次touchstart时候,便高高兴兴停止了动画(之一_transitionTime未传time时候会重置时间),所以先取消动画再移动位置
于是继续回到我们的end事件,
this.scrollTo(newX, newY);
如果没有超出边界便滑动到应该去的位置(这里有动画哦)
点击情况
当然,我们手指可能当前只不过想点击而已,这个时候就要触发相关的点击事件了,如果需要获取焦点,便获取焦点PS:他这里还模拟的fastclick想提升响应速度,但是他这样会引起大量BUG
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; }
运动参数
第一步的scrollTo其实可以放到move里面去,后面就用到了我们上文所说,根据动力加速度计算出来的动画参数:if (this.options.momentum && duration < 300) { momentumX = this.hasHorizontalScroll ? utils.momentum(this.x, this.startX, duration, this.maxScrollX, this.options.bounce ? this.wrapperWidth : 0) : { destination: newX, duration: 0 }; momentumY = this.hasVerticalScroll ? utils.momentum(this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0) : { destination: newY, duration: 0 }; newX = momentumX.destination; newY = momentumY.destination; time = Math.max(momentumX.duration, momentumY.duration); this.isInTransition = 1; }
那个snap不必关注,直接看下面,在此使用
this.scrollTo(newX, newY, time, easing);
开始运动,最后触发scrollend事件,这里如果超出边界会执行resetPosition方法还原的,不必关心
由此,我们几大核心事件点便学习结束了,轻松愉快哈
结语
今天学习了iScroll的几个核心点,我们下次来说下他的滚动条以及事件机制相关,整个iScroll就七七八八了相关文章推荐
- 【iScroll源码学习03】iScroll事件机制与滚动条的实现
- nginx 源码学习笔记(二十一)—— event 模块(二) ——事件驱动核心ngx_process_events_and_timers
- 【iScroll源码学习03】iScroll事件机制与滚动条的实现
- 【iScroll源码学习04】分离IScroll核心
- MyBatis源码学习系列:02-核心接口SqlSessionFactory和SqlSession
- nginx 源码学习笔记(二十一)—— event 模块(二) ——事件驱动核心ngx_process_events_and_timers
- nginx 源码学习笔记(二十一)—— event 模块(二) ——事件驱动核心ngx_process_events_and_timers
- ibatis源码学习1_整体设计和核心流程
- Python核心编程学习笔记-2016-08-13-02-绑定、静态方法和类方法
- python核心编程学习笔记-2016-08-02-02-模块动态导入
- java核心基础--jdk源码分析学习--HashSet
- java核心基础--jdk源码分析学习--Hashtable
- 【requireJS源码学习02】data-main加载的实现
- redis 源码学习(核心数据结构剖析)
- 从源码入手来学习EventBus 3事件总线机制
- Android中Activity触摸事件传递源码学习
- python核心编程学习笔记-2016-09-03-02-图形化用户界面编程(三)
- ExtJs源码分析与学习—ExtJs事件机制(二)
- java核心基础--jdk源码分析学习--Integer
- Spring IOC核心源码学习