【iScroll源码学习03】iScroll事件机制与滚动条的实现

【iScroll源码学习03】iScroll事件机制与滚动条的实现

前言

想不到又到周末了,周末的时间要抓紧学习才行,前几天咱们学习了iScroll几点基础知识:javascript

今天咱们来学习其事件机制以及滚动条的实现,完了后咱们iScroll就学习的差很少了,最后会抽离iScroll的精华部分组成一个阉割版iScrollhtml

并总结下iScroll的一些地方结束iScroll的学习,而后完全扑向nodeJS了前端

iScroll事件机制

咱们平时所说的事件机制其实应该分开,分红两块:java

① DOM的事件相关node

② 系统自建事件机制ios

在咱们前端的页面里面,最重要的固然是交互,交互其实就是一个个事件的体现,因此任何前端库的核心必定是其事件,iScroll就是由三大事件串联整个流程web

iScroll一样包括DOM事件以及自建事件,其中DOM事件即是浏览器的表现,而自建事件就是用户能够插一脚的地方了express

DOM事件参数盲点

iScroll DOM事件实现与可能让一些不熟悉javascript事件机制的同窗大跌眼镜(在与Aaron讨论前,我其实也摸不着头脑)数组

简单来讲,标准状况下咱们这样实现事件注册浏览器

el.addEventListener(type, fn, capture)

其中的全部参数都没有问题,惟独第二个参数,为何这么说呢?请看如下代码:

1 var eventObj = {}; 2 eventObj.a = 1; 3 document.addEventListener('click', eventObj)

各位以为这个代码有问题吗?第二个参数显然不是一个函数,可是function也是object呢,其实这样也是javascript规范之一,不知道只是咱们寡闻而已

这样写有如下好处,咱们的做用域就是咱们的对象:

复制代码
var eventObj = {}; eventObj.a = 1; eventObj.handleEvent = function () { alert(this.a); } document.addEventListener('click', eventObj) //这个代码点击会弹出1
复制代码

这个即是一个javascript规范,咱们传入的对象若是具备handleEvent 函数,便会执行,若是没有,这次注册便无心义,这样绑定的话,做用域便指向了eventObj

iScroll DOM 事件

有了以上知识,再说回iScroll的DOM事件:

① 构造函数会执行_initEvents方法初始化事件,咱们抽出咱们关心的一块:

1 eventType(this.wrapper, 'touchstart', this); 2 eventType(target, 'touchmove', this); 3 eventType(target, 'touchcancel', this); 4 eventType(target, 'touchend', this);
var eventType = remove ? utils.removeEvent : utils.addEvent

这个代码其实就是调用的addEvent方法:

1 me.addEvent = function (el, type, fn, capture) { 2 el.addEventListener(type, fn, !!capture); 3 };

那么iScroll事件绑定的具体点便捕捉到了:

能够看到咱们这里的fn是一个对象,可是不要担忧,咱们的具体的方法在此:

复制代码
 1 handleEvent: function (e) {  2 switch (e.type) {  3 case 'touchstart':  4 case 'MSPointerDown':  5 case 'mousedown':  6 this._start(e);  7 break;  8 case 'touchmove':  9 case 'MSPointerMove': 10 case 'mousemove': 11 this._move(e); 12 break; 13 case 'touchend': 14 case 'MSPointerUp': 15 case 'mouseup': 16 case 'touchcancel': 17 case 'MSPointerCancel': 18 case 'mousecancel': 19 this._end(e); 20 break; 21 case 'orientationchange': 22 case 'resize': 23 this._resize(); 24 break; 25 case 'transitionend': 26 case 'webkitTransitionEnd': 27 case 'oTransitionEnd': 28 case 'MSTransitionEnd': 29 this._transitionEnd(e); 30 break; 31 case 'wheel': 32 case 'DOMMouseScroll': 33 case 'mousewheel': 34 this._wheel(e); 35 break; 36 case 'keydown': 37 this._key(e); 38 break; 39 case 'click': 40 if (!e._constructed) { 41  e.preventDefault(); 42  e.stopPropagation(); 43  } 44 break; 45  } 46 }
复制代码

高大帅哈,如此整个iScroll的DOM事件相关就没问题了,在具体就回到了上次的三大事件点了

自定义事件机制

其实在咱们学习backbone时候咱们就提到了这块操做

复制代码
 1 var Events = Backbone.Events = {  2  3 on: function (name, callback, context) {  4 if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;  5 this._events || (this._events = {});  6 var events = this._events[name] || (this._events[name] = []);  7 events.push({ callback: callback, context: context, ctx: context || this });  8 return this;  9  }, 10 11 off: function (name, callback, context) { 12 var retain, ev, events, names, i, l, j, k; 13 if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this; 14 if (!name && !callback && !context) { 15 this._events = {}; 16 return this; 17  } 18 19 names = name ? [name] : _.keys(this._events); 20 for (i = 0, l = names.length; i < l; i++) { 21 name = names[i]; 22 if (events = this._events[name]) { 23 this._events[name] = retain = []; 24 if (callback || context) { 25 for (j = 0, k = events.length; j < k; j++) { 26 ev = events[j]; 27 if ((callback && callback !== ev.callback && callback !== ev.callback._callback) || 28 (context && context !== ev.context)) { 29  retain.push(ev); 30  } 31  } 32  } 33 if (!retain.length) delete this._events[name]; 34  } 35  } 36 37 return this; 38  }, 39 40 trigger: function (name) { 41 if (!this._events) return this; 42 var args = slice.call(arguments, 1); 43 if (!eventsApi(this, 'trigger', name, args)) return this; 44 var events = this._events[name]; 45 var allEvents = this._events.all; 46 if (events) triggerEvents(events, args); 47 if (allEvents) triggerEvents(allEvents, arguments); 48 return this; 49  }, 50 }; 51 52 // Regular expression used to split event strings. 53 var eventSplitter = /\s+/; 54 55 // Implement fancy features of the Events API such as multiple event 56 // names `"change blur"` and jQuery-style event maps `{change: action}` 57 // in terms of the existing API. 58 var eventsApi = function (obj, action, name, rest) { 59 if (!name) return true; 60 61 // Handle event maps. 62 if (typeof name === 'object') { 63 for (var key in name) { 64  obj[action].apply(obj, [key, name[key]].concat(rest)); 65  } 66 return false; 67 } 68 69 // Handle space separated event names. 70 if (eventSplitter.test(name)) { 71 var names = name.split(eventSplitter); 72 for (var i = 0, l = names.length; i < l; i++) { 73 obj[action].apply(obj, [names[i]].concat(rest)); 74 } 75 return false; 76 } 77 78 return true; 79 };
复制代码

所谓自建事件机制,实际上是唬人的,就是用一个数组保存各个阶段的函数,到特定阶段执行即可,iScroll这块作的尤为简单,并且又注册没有注销:

复制代码
 1 on: function (type, fn) {  2 if (!this._events[type]) {  3 this._events[type] = [];  4  }  5  6 this._events[type].push(fn);  7 },  8  9 _execEvent: function (type) { 10 if (!this._events[type]) { 11 return; 12  } 13 14 var i = 0, 15 l = this._events[type].length; 16 17 if (!l) { 18 return; 19  } 20 21 for (; i < l; i++) { 22 this._events[type][i].call(this); 23  } 24 },
复制代码

iScroll在构造函数中定义了_events这一对象,而后即可以开开心心使用on注册各类各样的事件了,其中每种事件对象是一个数组

定义好好,在特定阶段,好比touchstart阶段,便开开有没有注册相关事件,注册了便执行一发便可:

this._execEvent('scrollEnd');

这里要注意的是,他的this执行为iScroll,那么就可使用不少有用的属性了

至此,iScroll事件机制一块咱们便分析结束了,接下来来简单看看咱们关心的滚动条的实现

这里须要注意的一点是,这种实现的好处其实一个是方便在各个阶段注册、触发相关事件,主要原因仍是便于放出接口给外部调用

滚动条的实现

其实到这里,咱们队iScroll的解析都七七八八了,这里我不得不说,iScroll虽然动画感觉作的好之外,仍是可能致使一些问题

iScroll自己没什么问题,问题出在各类各样的浏览器中,据我读代码的感觉以及平时工做中遇到的问题,我相信项目中使用iScroll的朋友有可能

固然,这些问题出如今手机中:

① 当滑动碰到input可能出现滑动不顺的问题

② 滑动时候具备input时候滑动顺畅的话,input获取焦点不易

③ 点击时候可能出现问题(可能不能点击,可能双次点击)

④ 当你在ios点击时候碰到alert相似的东西,再点其它地方事件可能会重复触发

⑤ ......

固然以上问题只是个人猜想,是否真会致使问题还得通过验证,请各位不要搭理我,若是真有相似问题,获取其它问题请留言

上面扯了那么多也没有什么意义,咱们如今仍是来看滚动条的实现吧:

滚动条

iScroll为滚动条单独搞了一个类出来,由于在iScroll里面的滚动条是一等公民,具备如下特性:

① 鼠标中间滚动

② 可拖动滚动条

其实,多数时间以上功能能够取缔,尤为在手机上,其可点击区域仍是太小,单独用于手机的话,鼠标中间也无心义

PS:iscroll使用键盘上下键也能够滚动,真的是大而全的功能啊,可是无心义......至少在移动端意义不大,去掉还能够节约1k的流量

复制代码
 1 function Indicator(scroller, options) {  2 this.wrapper = typeof options.el == 'string' ? document.querySelector(options.el) : options.el;  3 this.wrapperStyle = this.wrapper.style;  4 this.indicator = this.wrapper.children[0];  5 this.indicatorStyle = this.indicator.style;  6 this.scroller = scroller;  7  8 this.options = {  9 listenX: true, 10 listenY: true, 11 interactive: false, 12 resize: true, 13 defaultScrollbars: false, 14 shrink: false, 15 fade: false, 16 speedRatioX: 0, 17 speedRatioY: 0 18  }; 19 20 for (var i in options) { 21 this.options[i] = options[i]; 22  } 23 24 this.sizeRatioX = 1; 25 this.sizeRatioY = 1; 26 this.maxPosX = 0; 27 this.maxPosY = 0; 28 29 if (this.options.interactive) { 30 if (!this.options.disableTouch) { 31 utils.addEvent(this.indicator, 'touchstart', this); 32 utils.addEvent(window, 'touchend', this); 33  } 34 if (!this.options.disablePointer) { 35 utils.addEvent(this.indicator, 'MSPointerDown', this); 36 utils.addEvent(window, 'MSPointerUp', this); 37  } 38 if (!this.options.disableMouse) { 39 utils.addEvent(this.indicator, 'mousedown', this); 40 utils.addEvent(window, 'mouseup', this); 41  } 42  } 43 44 if (this.options.fade) { 45 this.wrapperStyle[utils.style.transform] = this.scroller.translateZ; 46 this.wrapperStyle[utils.style.transitionDuration] = utils.isBadAndroid ? '0.001s' : '0ms'; 47 this.wrapperStyle.opacity = '0'; 48  } 49 }
复制代码

设个就是滚动条的构造函数,这有一个关键点:

interactiveScrollbars: true

默认咱们的滚动条是不会具备滚动等事件的,若是设置了此参数便具备可拖动特性了

复制代码
 1 if (this.options.interactive) {  2 if (!this.options.disableTouch) {  3 utils.addEvent(this.indicator, 'touchstart', this);  4 utils.addEvent(window, 'touchend', this);  5  }  6 if (!this.options.disablePointer) {  7 utils.addEvent(this.indicator, 'MSPointerDown', this);  8 utils.addEvent(window, 'MSPointerUp', this);  9  } 10 if (!this.options.disableMouse) { 11 utils.addEvent(this.indicator, 'mousedown', this); 12 utils.addEvent(window, 'mouseup', this); 13  } 14 }
复制代码

这里虽然给滚动条绑定的事件,可是会一并操做咱们的body主体,可是咱们后面会直接忽略这步操做

1 if (this.options.fade) { 2 this.wrapperStyle[utils.style.transform] = this.scroller.translateZ; 3 this.wrapperStyle[utils.style.transitionDuration] = utils.isBadAndroid ? '0.001s' : '0ms'; 4 this.wrapperStyle.opacity = '0'; 5 }

而后,会给滚动条一个渐隐的效果,这个影响较小,直接使用了CSS3实现

下面继续实现了他的事件handleEvent

View Code

接下来又是touch几个事件了:

View Code

这个地方因为咱们后面不会实现便直接不予关注了

结语

忽然来了几个BUG,等下要发布测试环境了,今天暂时到这里,咱们下次继续好了,下次咱们就直接分离iScroll了,抽出咱们想要的功能

相关文章
相关标签/搜索