前言

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

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

并总结下iScroll的一些地方结束iScroll的学习,然后彻底扑向nodeJS了

iScroll事件机制

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

① DOM的事件相关

② 系统自建事件机制

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

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

DOM事件参数盲点

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

简单来说,标准情况下我们这样实现事件注册

el.addEventListener(type, fn, capture)

其中的所有参数都没有问题,唯独第二个参数,为什么这么说呢?请看以下代码:

 var eventObj = {};
eventObj.a = 1;
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方法初始化事件,我们抽出我们关心的一块:

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

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

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

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

可以看到我们这里的fn是一个对象,但是不要担心,我们的具体的方法在此:

 handleEvent: function (e) {
switch (e.type) {
case 'touchstart':
case 'MSPointerDown':
case 'mousedown':
this._start(e);
break;
case 'touchmove':
case 'MSPointerMove':
case 'mousemove':
this._move(e);
break;
case 'touchend':
case 'MSPointerUp':
case 'mouseup':
case 'touchcancel':
case 'MSPointerCancel':
case 'mousecancel':
this._end(e);
break;
case 'orientationchange':
case 'resize':
this._resize();
break;
case 'transitionend':
case 'webkitTransitionEnd':
case 'oTransitionEnd':
case 'MSTransitionEnd':
this._transitionEnd(e);
break;
case 'wheel':
case 'DOMMouseScroll':
case 'mousewheel':
this._wheel(e);
break;
case 'keydown':
this._key(e);
break;
case 'click':
if (!e._constructed) {
e.preventDefault();
e.stopPropagation();
}
break;
}
}

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

自定义事件机制

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

 var Events = Backbone.Events = {

   on: function (name, callback, context) {
if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
this._events || (this._events = {});
var events = this._events[name] || (this._events[name] = []);
events.push({ callback: callback, context: context, ctx: context || this });
return this;
}, off: function (name, callback, context) {
var retain, ev, events, names, i, l, j, k;
if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
if (!name && !callback && !context) {
this._events = {};
return this;
} names = name ? [name] : _.keys(this._events);
for (i = 0, l = names.length; i < l; i++) {
name = names[i];
if (events = this._events[name]) {
this._events[name] = retain = [];
if (callback || context) {
for (j = 0, k = events.length; j < k; j++) {
ev = events[j];
if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
(context && context !== ev.context)) {
retain.push(ev);
}
}
}
if (!retain.length) delete this._events[name];
}
} return this;
}, trigger: function (name) {
if (!this._events) return this;
var args = slice.call(arguments, 1);
if (!eventsApi(this, 'trigger', name, args)) return this;
var events = this._events[name];
var allEvents = this._events.all;
if (events) triggerEvents(events, args);
if (allEvents) triggerEvents(allEvents, arguments);
return this;
},
}; // Regular expression used to split event strings.
var eventSplitter = /\s+/; // Implement fancy features of the Events API such as multiple event
// names `"change blur"` and jQuery-style event maps `{change: action}`
// in terms of the existing API.
var eventsApi = function (obj, action, name, rest) {
if (!name) return true; // Handle event maps.
if (typeof name === 'object') {
for (var key in name) {
obj[action].apply(obj, [key, name[key]].concat(rest));
}
return false;
} // Handle space separated event names.
if (eventSplitter.test(name)) {
var names = name.split(eventSplitter);
for (var i = 0, l = names.length; i < l; i++) {
obj[action].apply(obj, [names[i]].concat(rest));
}
return false;
} return true;
};

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

 on: function (type, fn) {
if (!this._events[type]) {
this._events[type] = [];
} this._events[type].push(fn);
}, _execEvent: function (type) {
if (!this._events[type]) {
return;
} var i = 0,
l = this._events[type].length; if (!l) {
return;
} for (; i < l; i++) {
this._events[type][i].call(this);
}
},

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

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

this._execEvent('scrollEnd');

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

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

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

滚动条的实现

其实到这里,我们队iScroll的解析都七七八八了,这里我不得不说,iScroll虽然动画感受做的好以外,还是可能导致一些问题

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

当然,这些问题出现在手机中:

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

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

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

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

⑤ ......

当然以上问题只是我的猜测,是否真会导致问题还得经过验证,请各位不要搭理我,如果真有类似问题,获取其它问题请留言

上面扯了那么多也没有什么意义,我们现在还是来看滚动条的实现吧:

滚动条

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

① 鼠标中间滚动

② 可拖动滚动条

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

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

 function Indicator(scroller, options) {
this.wrapper = typeof options.el == 'string' ? document.querySelector(options.el) : options.el;
this.wrapperStyle = this.wrapper.style;
this.indicator = this.wrapper.children[0];
this.indicatorStyle = this.indicator.style;
this.scroller = scroller; this.options = {
listenX: true,
listenY: true,
interactive: false,
resize: true,
defaultScrollbars: false,
shrink: false,
fade: false,
speedRatioX: 0,
speedRatioY: 0
}; for (var i in options) {
this.options[i] = options[i];
} this.sizeRatioX = 1;
this.sizeRatioY = 1;
this.maxPosX = 0;
this.maxPosY = 0; if (this.options.interactive) {
if (!this.options.disableTouch) {
utils.addEvent(this.indicator, 'touchstart', this);
utils.addEvent(window, 'touchend', this);
}
if (!this.options.disablePointer) {
utils.addEvent(this.indicator, 'MSPointerDown', this);
utils.addEvent(window, 'MSPointerUp', this);
}
if (!this.options.disableMouse) {
utils.addEvent(this.indicator, 'mousedown', this);
utils.addEvent(window, 'mouseup', this);
}
} if (this.options.fade) {
this.wrapperStyle[utils.style.transform] = this.scroller.translateZ;
this.wrapperStyle[utils.style.transitionDuration] = utils.isBadAndroid ? '0.001s' : '0ms';
this.wrapperStyle.opacity = '0';
}
}

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

interactiveScrollbars: true

默认我们的滚动条是不会具有滚动等事件的,如果设置了此参数便具有可拖动特性了

 if (this.options.interactive) {
if (!this.options.disableTouch) {
utils.addEvent(this.indicator, 'touchstart', this);
utils.addEvent(window, 'touchend', this);
}
if (!this.options.disablePointer) {
utils.addEvent(this.indicator, 'MSPointerDown', this);
utils.addEvent(window, 'MSPointerUp', this);
}
if (!this.options.disableMouse) {
utils.addEvent(this.indicator, 'mousedown', this);
utils.addEvent(window, 'mouseup', this);
}
}

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

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

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

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

 handleEvent: function (e) {
switch (e.type) {
case 'touchstart':
case 'MSPointerDown':
case 'mousedown':
this._start(e);
break;
case 'touchmove':
case 'MSPointerMove':
case 'mousemove':
this._move(e);
break;
case 'touchend':
case 'MSPointerUp':
case 'mouseup':
case 'touchcancel':
case 'MSPointerCancel':
case 'mousecancel':
this._end(e);
break;
}
}

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

 _start: function (e) {
var point = e.touches ? e.touches[0] : e; e.preventDefault();
e.stopPropagation(); this.transitionTime(); this.initiated = true;
this.moved = false;
this.lastPointX = point.pageX;
this.lastPointY = point.pageY; this.startTime = utils.getTime(); if (!this.options.disableTouch) {
utils.addEvent(window, 'touchmove', this);
}
if (!this.options.disablePointer) {
utils.addEvent(window, 'MSPointerMove', this);
}
if (!this.options.disableMouse) {
utils.addEvent(window, 'mousemove', this);
} this.scroller._execEvent('beforeScrollStart');
}, _move: function (e) {
var point = e.touches ? e.touches[0] : e,
deltaX, deltaY,
newX, newY,
timestamp = utils.getTime(); if (!this.moved) {
this.scroller._execEvent('scrollStart');
} this.moved = true; deltaX = point.pageX - this.lastPointX;
this.lastPointX = point.pageX; deltaY = point.pageY - this.lastPointY;
this.lastPointY = point.pageY; newX = this.x + deltaX;
newY = this.y + deltaY; this._pos(newX, newY); // INSERT POINT: indicator._move e.preventDefault();
e.stopPropagation();
}, _end: function (e) {
if (!this.initiated) {
return;
} this.initiated = false; e.preventDefault();
e.stopPropagation(); utils.removeEvent(window, 'touchmove', this);
utils.removeEvent(window, 'MSPointerMove', this);
utils.removeEvent(window, 'mousemove', this); if (this.scroller.options.snap) {
var snap = this.scroller._nearestSnap(this.scroller.x, this.scroller.y); var time = this.options.snapSpeed || Math.max(
Math.max(
Math.min(Math.abs(this.scroller.x - snap.x), 1000),
Math.min(Math.abs(this.scroller.y - snap.y), 1000)
), 300); if (this.scroller.x != snap.x || this.scroller.y != snap.y) {
this.scroller.directionX = 0;
this.scroller.directionY = 0;
this.scroller.currentPage = snap;
this.scroller.scrollTo(snap.x, snap.y, time, this.scroller.options.bounceEasing);
}
} if (this.moved) {
this.scroller._execEvent('scrollEnd');
}
},

这个地方由于我们后面不会实现便直接不予关注了

结语

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

【iScroll源码学习03】iScroll事件机制与滚动条的实现的更多相关文章

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

    前言 最近几天我们前前后后基本将iScroll源码学的七七八八了,文章中未涉及的各位就要自己去看了 1. [iScroll源码学习03]iScroll事件机制与滚动条的实现 2. [iScroll源码 ...

  2. 【iScroll源码学习02】分解iScroll三个核心事件点

    前言 最近两天看到很多的总结性发言,我想想今年好像我的变化挺大的,是不是该晚上来水一发呢?嗯,决定了,晚上来水一发! 上周六,我们简单模拟了下iScroll的实现,周日我们开始了学习iScroll的源 ...

  3. 【iScroll源码学习01】准备阶段 - 叶小钗

    [iScroll源码学习01]准备阶段 - 叶小钗 时间 2013-12-29 18:41:00 博客园-原创精华区 原文  http://www.cnblogs.com/yexiaochai/p/3 ...

  4. 【iScroll源码学习00】模拟iScroll

    前言 相信对移动端有了解的朋友对iScroll这个库非常熟悉吧,今天我们就来说下我们移动页面的iScroll化 iScroll是我们必学框架之一,我们这次先根据iScroll功能自己实现其功能,然后再 ...

  5. 从Chrome源码看浏览器的事件机制

    .aligncenter { clear: both; display: block; margin-left: auto; margin-right: auto } .crayon-line spa ...

  6. 【iScroll源码学习01】准备阶段

    前言 我们昨天初步了解了为什么会出现iScroll:[SPA]移动站点APP化研究之上中下页面的iScroll化(上),然后简单的写了一个demo来模拟iScroll,其中了解到了以下知识点: ① v ...

  7. iscroll源码学习(1)

    iscroll是移端端开发的两大利器之一(另一个是fastclick),为了将它整合的avalon,需要对它认真学习一番.下面是我的笔记. 第一天看的是它的工具类util.js //用于做函数节流 v ...

  8. dubbo 源码学习1 服务发布机制

    1.源码版本:2.6.1 源码demo中采用的是xml式的发布方式,在dubbo的 DubboNamespaceHandler 中定义了Spring Framework 的扩展标签,即 <dub ...

  9. zepto源码学习-03 $()

    在第一篇的时候提到过关于$()的用法,一个接口有很多重载,用法有很多种,总结了下,大概有一以下几种 1.$(selector,context?) 传入一个选择器返回一个zepto对象 2.$(func ...

随机推荐

  1. 深入理解PHP内核(六)函数的定义、传参及返回值

    一.函数的定义 用户函数的定义从function 关键字开始,如下 function foo($var) { echo $var; } 1.词法分析 在Zend/zend_language_scann ...

  2. 深入理解CSS变形transform(3d)

    × 目录 [1]坐标轴 [2]透视 [3]变形函数 [4]透视函数 [5]变形原点 [6]背景可见 [7]变形风格 前面的话 本文将详细介绍关于transform变形3D的内容,但需以了解transf ...

  3. 你用过这种奇葩的C#注释吗?如何看待

    本人虽然不是专业开发人员,也非专业出身,但一直使用C#堆码,解决自己日常的小问题.包括自己的研究,也是用C#来实现和测试.对C#是情有独钟.虽然C#的很多高级技术不会用,也不太懂,但总归是知道,耳闻目 ...

  4. C++笔记(3):一些C++的基础知识点

     前言: 找工作需要,最近看了下一些C++的基本概念,为范磊的<零起点学通C++>,以下是一些笔记. 内容: delete p;只是删除指针p指向内存区,并不是删除指针p,所以p还是可以用 ...

  5. CentOS7 Java安装

    CentOS7 Java安装 CentOS7 Java安装 Download 从Oracle下载jdk-8u31-linux-x64.rpm Install 御载 执行如下命令 java -versi ...

  6. MFC抓网页

    CString chinachar_str("读取的东西:"); CInternetSession sion(NULL,); CHttpFile *http=NULL; CStri ...

  7. c#实现查询程序运行线程数

    class Program { static void Main(string[] args) { List<Thread> list = new List<Thread>() ...

  8. SQL约束

    SQL约束: 非空约束:就是不能为null: 主键约束(PK):唯一,不重复,并且不为空: 唯一约束:唯一,允许为空,但只能出现一次: 默认约束:如果不给值,默认值: 检查约束:范围以及格式限制: 外 ...

  9. [锋利JQ]-图片提示效果

    新知识点: 在HTML-Dom中 "style" 属性,是所有HTML标签共有的一个对象属性,这个对象属性可以为元素设置内嵌样式. style是元素的属性,但是它自身则是一个对象, ...

  10. c#单例模式的实现

    单例模式定义:一个类有且仅有一个实例,并且自行实例化向整个系统提供. 实现要点: 1.是单例模式的类只提供私有的构造函数.   2.是类定义中含有一个该类的静态私有对象. 3.是该类提供了一个静态的共 ...