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

2019年11月22日 阅读数:74
这篇文章主要向大家介绍【iScroll源码学习01】准备阶段,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

前言

咱们昨天初步了解了为何会出现iScroll:【SPA】移动站点APP化研究之上中下页面的iScroll化(上),而后简单的写了一个demo来模拟iScroll,其中了解到了如下知识点:javascript

① viewport相关知识点(device-width等)css

② CSS3硬件加速html

③ 如何暂停CSS动画java

④ e.preventDefault致使文本不能获取焦点解决方案jquery

......web

固然,咱们写的demo天然不能和iScroll自己的代码比肩,可是demo过程当中咱们也大概了解了iScroll代码过程当中须要注意的一些问题chrome

因而,今天让咱们进入iScroll的学习吧ubuntu

初探iScroll

  1 <!DOCTYPE html>
  2 <html>
  3 <head>
  4 <meta charset="utf-8">
  5 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">
  6 
  7 <title>iScroll demo: scrollbars</title>
  8 
  9 <script type="text/javascript" src="../../build/iscroll.js"></script>
 10 
 11 
 12 <script type="text/javascript">
 13 
 14 var myScroll;
 15 
 16 function loaded () {
 17     myScroll = new IScroll('#wrapper', {
 18         scrollbars: true,
 19         mouseWheel: true,
 20         interactiveScrollbars: true,
 21         shrinkScrollbars: 'scale',
 22         fadeScrollbars: true
 23     });
 24 }
 25 
 26 document.addEventListener('touchmove', function (e) { e.preventDefault(); }, false);
 27 
 28 </script>
 29 
 30 <style type="text/css">
 31 * {
 32     -webkit-box-sizing: border-box;
 33     -moz-box-sizing: border-box;
 34     box-sizing: border-box;
 35 }
 36 
 37 html {
 38     -ms-touch-action: none;
 39 }
 40 
 41 body,ul,li {
 42     padding: 0;
 43     margin: 0;
 44     border: 0;
 45 }
 46 
 47 body {
 48     font-size: 12px;
 49     font-family: ubuntu, helvetica, arial;
 50     overflow: hidden; /* this is important to prevent the whole page to bounce */
 51 }
 52 
 53 #header {
 54     position: absolute;
 55     z-index: 2;
 56     top: 0;
 57     left: 0;
 58     width: 100%;
 59     height: 45px;
 60     line-height: 45px;
 61     background: #CD235C;
 62     padding: 0;
 63     color: #eee;
 64     font-size: 20px;
 65     text-align: center;
 66     font-weight: bold;
 67 }
 68 
 69 #footer {
 70     position: absolute;
 71     z-index: 2;
 72     bottom: 0;
 73     left: 0;
 74     width: 100%;
 75     height: 48px;
 76     background: #444;
 77     padding: 0;
 78     border-top: 1px solid #444;
 79 }
 80 
 81 #wrapper {
 82     position: absolute;
 83     z-index: 1;
 84     top: 45px;
 85     bottom: 48px;
 86     left: 0;
 87     width: 100%;
 88     background: #ccc;
 89     overflow: hidden;
 90 }
 91 
 92 #scroller {
 93     position: absolute;
 94     z-index: 1;
 95     -webkit-tap-highlight-color: rgba(0,0,0,0);
 96     width: 100%;
 97     -webkit-transform: translateZ(0);
 98     -moz-transform: translateZ(0);
 99     -ms-transform: translateZ(0);
100     -o-transform: translateZ(0);
101     transform: translateZ(0);
102     -webkit-touch-callout: none;
103     -webkit-user-select: none;
104     -moz-user-select: none;
105     -ms-user-select: none;
106     user-select: none;
107     -webkit-text-size-adjust: none;
108     -moz-text-size-adjust: none;
109     -ms-text-size-adjust: none;
110     -o-text-size-adjust: none;
111     text-size-adjust: none;
112 }
113 
114 #scroller ul {
115     list-style: none;
116     padding: 0;
117     margin: 0;
118     width: 100%;
119     text-align: left;
120 }
121 
122 #scroller li {
123     padding: 0 10px;
124     height: 40px;
125     line-height: 40px;
126     border-bottom: 1px solid #ccc;
127     border-top: 1px solid #fff;
128     background-color: #fafafa;
129     font-size: 14px;
130 }
131 
132 </style>
133 </head>
134 <body onload="loaded()">
135 <div id="header">iScroll</div>
136 
137 <div id="wrapper">
138     <div id="scroller">
139         <ul>
140             <li>Pretty row 1</li>
141             <li>Pretty row 2</li>
142             <li>Pretty row 3</li>
143             <li>Pretty row 4</li>
144             <li>Pretty row 5</li>
145             <li>Pretty row 6</li>
146             <li>Pretty row 7</li>
147             <li>Pretty row 8</li>
148             <li>Pretty row 9</li>
149             <li>Pretty row 10</li>
150             <li>Pretty row 11</li>
151             <li>Pretty row 12</li>
152             <li>Pretty row 13</li>
153             <li>Pretty row 14</li>
154             <li>Pretty row 15</li>
155             <li>Pretty row 16</li>
156             <li>Pretty row 17</li>
157             <li>Pretty row 18</li>
158             <li>Pretty row 19</li>
159             <li>Pretty row 20</li>
160             <li>Pretty row 21</li>
161             <li>Pretty row 22</li>
162             <li>Pretty row 23</li>
163             <li>Pretty row 24</li>
164             <li>Pretty row 25</li>
165             <li>Pretty row 26</li>
166             <li>Pretty row 27</li>
167             <li>Pretty row 28</li>
168             <li>Pretty row 29</li>
169             <li>Pretty row 30</li>
170             <li>Pretty row 31</li>
171             <li>Pretty row 32</li>
172             <li>Pretty row 33</li>
173             <li>Pretty row 34</li>
174             <li>Pretty row 35</li>
175             <li>Pretty row 36</li>
176             <li>Pretty row 37</li>
177             <li>Pretty row 38</li>
178             <li>Pretty row 39</li>
179             <li>Pretty row 40</li>
180             <li>Pretty row 41</li>
181             <li>Pretty row 42</li>
182             <li>Pretty row 43</li>
183             <li>Pretty row 44</li>
184             <li>Pretty row 45</li>
185             <li>Pretty row 46</li>
186             <li>Pretty row 47</li>
187             <li>Pretty row 48</li>
188             <li>Pretty row 49</li>
189             <li>Pretty row 50</li>
190 
191       <li>Pretty row 1</li>
192             <li>Pretty row 2</li>
193             <li>Pretty row 3</li>
194             <li>Pretty row 4</li>
195             <li>Pretty row 5</li>
196             <li>Pretty row 6</li>
197             <li>Pretty row 7</li>
198             <li>Pretty row 8</li>
199             <li>Pretty row 9</li>
200             <li>Pretty row 10</li>
201             <li>Pretty row 11</li>
202             <li>Pretty row 12</li>
203             <li>Pretty row 13</li>
204             <li>Pretty row 14</li>
205             <li>Pretty row 15</li>
206             <li>Pretty row 16</li>
207             <li>Pretty row 17</li>
208             <li>Pretty row 18</li>
209             <li>Pretty row 19</li>
210             <li>Pretty row 20</li>
211             <li>Pretty row 21</li>
212             <li>Pretty row 22</li>
213             <li>Pretty row 23</li>
214             <li>Pretty row 24</li>
215             <li>Pretty row 25</li>
216             <li>Pretty row 26</li>
217             <li>Pretty row 27</li>
218             <li>Pretty row 28</li>
219             <li>Pretty row 29</li>
220             <li>Pretty row 30</li>
221             <li>Pretty row 31</li>
222             <li>Pretty row 32</li>
223             <li>Pretty row 33</li>
224             <li>Pretty row 34</li>
225             <li>Pretty row 35</li>
226             <li>Pretty row 36</li>
227             <li>Pretty row 37</li>
228             <li>Pretty row 38</li>
229             <li>Pretty row 39</li>
230             <li>Pretty row 40</li>
231             <li>Pretty row 41</li>
232             <li>Pretty row 42</li>
233             <li>Pretty row 43</li>
234             <li>Pretty row 44</li>
235             <li>Pretty row 45</li>
236             <li>Pretty row 46</li>
237             <li>Pretty row 47</li>
238             <li>Pretty row 48</li>
239             <li>Pretty row 49</li>
240             <li>Pretty row 50</li>
241 
242       <li>Pretty row 1</li>
243             <li>Pretty row 2</li>
244             <li>Pretty row 3</li>
245             <li>Pretty row 4</li>
246             <li>Pretty row 5</li>
247             <li>Pretty row 6</li>
248             <li>Pretty row 7</li>
249             <li>Pretty row 8</li>
250             <li>Pretty row 9</li>
251             <li>Pretty row 10</li>
252             <li>Pretty row 11</li>
253             <li>Pretty row 12</li>
254             <li>Pretty row 13</li>
255             <li>Pretty row 14</li>
256             <li>Pretty row 15</li>
257             <li>Pretty row 16</li>
258             <li>Pretty row 17</li>
259             <li>Pretty row 18</li>
260             <li>Pretty row 19</li>
261             <li>Pretty row 20</li>
262             <li>Pretty row 21</li>
263             <li>Pretty row 22</li>
264             <li>Pretty row 23</li>
265             <li>Pretty row 24</li>
266             <li>Pretty row 25</li>
267             <li>Pretty row 26</li>
268             <li>Pretty row 27</li>
269             <li>Pretty row 28</li>
270             <li>Pretty row 29</li>
271             <li>Pretty row 30</li>
272             <li>Pretty row 31</li>
273             <li>Pretty row 32</li>
274             <li>Pretty row 33</li>
275             <li>Pretty row 34</li>
276             <li>Pretty row 35</li>
277             <li>Pretty row 36</li>
278             <li>Pretty row 37</li>
279             <li>Pretty row 38</li>
280             <li>Pretty row 39</li>
281             <li>Pretty row 40</li>
282             <li>Pretty row 41</li>
283             <li>Pretty row 42</li>
284             <li>Pretty row 43</li>
285             <li>Pretty row 44</li>
286             <li>Pretty row 45</li>
287             <li>Pretty row 46</li>
288             <li>Pretty row 47</li>
289             <li>Pretty row 48</li>
290             <li>Pretty row 49</li>
291             <li>Pretty row 50</li>
292 
293       <li>Pretty row 1</li>
294             <li>Pretty row 2</li>
295             <li>Pretty row 3</li>
296             <li>Pretty row 4</li>
297             <li>Pretty row 5</li>
298             <li>Pretty row 6</li>
299             <li>Pretty row 7</li>
300             <li>Pretty row 8</li>
301             <li>Pretty row 9</li>
302             <li>Pretty row 10</li>
303             <li>Pretty row 11</li>
304             <li>Pretty row 12</li>
305             <li>Pretty row 13</li>
306             <li>Pretty row 14</li>
307             <li>Pretty row 15</li>
308             <li>Pretty row 16</li>
309             <li>Pretty row 17</li>
310             <li>Pretty row 18</li>
311             <li>Pretty row 19</li>
312             <li>Pretty row 20</li>
313             <li>Pretty row 21</li>
314             <li>Pretty row 22</li>
315             <li>Pretty row 23</li>
316             <li>Pretty row 24</li>
317             <li>Pretty row 25</li>
318             <li>Pretty row 26</li>
319             <li>Pretty row 27</li>
320             <li>Pretty row 28</li>
321             <li>Pretty row 29</li>
322             <li>Pretty row 30</li>
323             <li>Pretty row 31</li>
324             <li>Pretty row 32</li>
325             <li>Pretty row 33</li>
326             <li>Pretty row 34</li>
327             <li>Pretty row 35</li>
328             <li>Pretty row 36</li>
329             <li>Pretty row 37</li>
330             <li>Pretty row 38</li>
331             <li>Pretty row 39</li>
332             <li>Pretty row 40</li>
333             <li>Pretty row 41</li>
334             <li>Pretty row 42</li>
335             <li>Pretty row 43</li>
336             <li>Pretty row 44</li>
337             <li>Pretty row 45</li>
338             <li>Pretty row 46</li>
339             <li>Pretty row 47</li>
340             <li>Pretty row 48</li>
341             <li>Pretty row 49</li>
342             <li>Pretty row 50</li>
343         </ul>
344     </div>
345 </div>
346 
347 <div id="footer"></div>
348 </body>
349 </html>
View Code

http://sandbox.runjs.cn/show/pscjy3a3浏览器

下面是他初始化时候的核心代码:缓存

 1 var myScroll;
 2 function loaded () {
 3     myScroll = new IScroll('#wrapper', {
 4         scrollbars: true,
 5         mouseWheel: true,
 6         interactiveScrollbars: true,
 7         shrinkScrollbars: 'scale',
 8         fadeScrollbars: true
 9     });
10 }
11 document.addEventListener('touchmove', function (e) { e.preventDefault(); }, false);

真不得不说,这个滑动起来感受挺不错的,第一感受有几大特色:

① 顺畅

② 连续拖动会加速滑动

③ 没有BUG :)

看看他的构造函数,而后去网上找一点参数说明(Iscroll应用文档):

hScroll: true, //是否水平滚动
vScroll: true, //是否垂直滚动
x: 0, //滚动水平初始位置
y: 0, //滚动垂直初始位置
snap: true, //值能够为true或是DOM元素的tagname,当为true时,对齐的坐标会根据可滚动的位置和滚动区域计算获得可滑动几页,若是为tagname,则滑动会对齐到元素上
bounce: true, //是否超过实际位置反弹
bounceLock: false, //当内容少于滚动是否能够反弹,这个实际用处不大
momentum: true, //动量效果,拖动惯性
lockDirection: true, //当水平滚动和垂直滚动同时生效时,当拖动开始是否锁定另外一边的拖动
useTransform: true, //是否使用CSS形变
useTransition: false, //是否使用CSS变换
topOffset: 0, //已经滚动的基准值(通常状况用不到)
checkDOMChanges: false, //是否自动检测内容变化(这个检测不是很准)

//Scrollbar相关参数,经过scrollbar这些参数能够配置iscroll的滚动条,经过scrollbarClass能够本身定义一套滚动条的样式。
hScrollbar: true, //是否显示水平滚动条
vScrollbar: true, //同上垂直滚动条
fixedScrollbar: isAndroid, //对andriod的fixed
hideScrollbar: isIDevice, //是否隐藏滚动条
fadeScrollbar: isIDevice && has3d, //滚动条是否渐隐渐显
scrollbarClass: '', //自定义滚动条的样式名

//Zoom放大相关的参数,经过它,对于一个固定显示图片区域的相似应用,能够很是简单的作到固定滚动,包括两指放大的应用。
zoom: false, //默认是否放大
zoomMin: 1, //放大的最小倍数
zoomMax: 4, //最大倍数
doubleTapZoom: 2, //双触放大几倍
wheelAction: 'scroll', //鼠标滚动行为(还能够是zoom)

//自定义Events相关参数 
onRefresh: null, //refresh 的回调,关于自身什么时候调用refresh 后面会继续谈到
onBeforeScrollStart: function(e){ e.preventDefault(); }, //开始滚动前的时间回调,默认是阻止浏览器默认行为
onScrollStart: null, //开始滚动的回调
onBeforeScrollMove: null, //在内容移动前的回调
onScrollMove: null, //内容移动的回调
onBeforeScrollEnd: null, //在滚动结束前的回调
onScrollEnd: null, //在滚动完成后的回调
onTouchEnd: null, //手离开屏幕后的回调
onDestroy: null, //销毁实例的回调
onZoomStart: null, //开始放大前的回调
onZoom: null, //放大的回调
onZoomEnd: null //放大完成后的回调
Iscroll 提供的调用方法

destroy 
顾名思义,是用来销毁你实例化的iScroll 实例,包括以前绑定的全部iscroll 事件。 

refresh 
这个方法很是有用,当你的滚动区域的内容发生改变 或是 滚动区域不正确,都用经过调用refresh 来使得iscroll 从新计算滚动的区域,包括滚动条,来使得iscroll 适合当前的dom。 

scrollTo 
这个方法接受4个参数 x, y, time, relative x 为移动的x轴坐标,y为移动的y轴坐标, time为移动时间,relative表示是否相对当前位置。 

scrollToElement 
这个方法其实是对scrollTo的进一步封装,接受两个参数(el,time),el为须要滚动到的元素引用,time为滚动时间。 

scrollToPage 
此方法接受三个参数(pageX,pageY,time) 当滚动内容的高宽大于滚动范围时,iscroll 会自动分页,而后就能使用scrollToPage方法滚动到页面。固然,当hscroll 为false 的时候,不能左右滚动。pageX这个参数就失去效果 

disable 
调用这个方法会当即中止动画滚动,而且把滚动位置还原成0,取消绑定touchmove, touchend、touchcancel事件。 

enable 
调用这个方法,使得iscroll恢复默认正常状态 

stop 
当即中止动画 

zoom 
改变内容的大小倍数,此方法接受4个参数,x,y,scale,time 分别表示的意思为,放大的基准坐标,以及放大倍数,动画时间 

isReady 
当iscroll 没有处于正在滚动,没有移动过,没有改变大小时,此值为true

功能很是丰富啊,对于应用来讲够用了,可是一些功能我这里用不到,就忽略了

功能很好,size为48k,压缩后稍微好一点,将近2000行的代码,做为基础库来讲,有点大了,比整个zepto还大

并且整个库的注释写的很差,好像压根就没写......不知道阅读上会不会有障碍,因而咱们进入源码

iScroll笔记

requestAnimationFrame

1 var rAF = window.requestAnimationFrame    ||
2     window.webkitRequestAnimationFrame    ||
3     window.mozRequestAnimationFrame        ||
4     window.oRequestAnimationFrame        ||
5     window.msRequestAnimationFrame        ||
6     function (callback) { window.setTimeout(callback, 1000 / 60); };

这段代码是要作能力检测的,这里来讲一下requestAnimationFrame这个东西(参考:http://www.kimhou.com/?p=155

在jquery中javascript动画是经过定时器(settimeout)实现的,没一个时间点改变一点style,而CSS3后便推出了transition以及animation开始实现动画

咱们昨天提到的硬件加速,也是CSS3相关的东西。CSS3动画效率与顺畅度比Js高,因此如今动画开始楚河汉界了

js的好处是能够很好的控制动画状态、css动画带来的性能较高,可是控制度就低一点(是很低)

定时器实现动画

 1 <html xmlns="http://www.w3.org/1999/xhtml">
 2 <head>
 3   <title></title>
 4 </head>
 5 <body>
 6   <div id="el" style="position: absolute;">
 7     Javascript时钟实现动画
 8   </div>
 9   <script src="zepto.js" type="text/javascript"></script>
10   <script type="text/javascript">
11     var pos = 0;
12     var final = 200;
13     var dir = 0;
14     var el = $('#el');
15     function upLeft() {
16       var left = parseInt(el.css('left')) || 0;
17       if (left >= final) dir = 1;
18       if (left <= pos) dir = 0;
19 
20       if (dir == 0) {
21         left++;
22         el.css('left', left + 'px');
23         setTimeout(upLeft);
24       } else {
25         left--;
26         el.css('left', left + 'px');
27         setTimeout(upLeft);
28       }
29     }
30     upLeft();
31   </script>
32 </body>
33 </html>

效果见下面

这即是使用javascript实现的一个最简单的动画,各位看到了,里面的定时器不停的在运动,性能不差的话我就更名叫素还真了

这个阶段,比较棘手的问题每每在延迟的计算,间隔要短因此动画顺畅,可是浏览器渲染也得耗费时间,这个就要求每次变化留给浏览器的时间够长了(60HZ/75Hz)

因此以前javascript的间隔通常为20左右,这个时候的动画比较流畅,这个数字与浏览器的频率比较接近(1000/60)

function (callback) { window.setTimeout(callback, 1000 / 60); }

可是经过前面对时钟的学习,咱们知道settimeout只是将回调函数加入UI线程队列,那么同一时间有多个动画待执行的话,延迟就发生了,效果也会打折扣

这里原来用js实现坦克大战的朋友就会有所体会了

javascript问题解决

CSS的transition与animations的优点在于浏览器知道哪些动画将会发生,因此动画会获得正确的间隔来刷新UI(javascript固然不知道)

因而这里就多了一个方法:RequestAnimationFrame,他能够告诉浏览器本身要执行动画了,因而js的动画事实上获得了优化

RequestAnimationFrame接受一个参数,也就是屏幕重绘前会调用的函数,这个函数用来改变dom样式,这个方法使用有点相似于settimeout

 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 2 <html xmlns="http://www.w3.org/1999/xhtml">
 3 <head>
 4   <title></title>
 5 </head>
 6 <body>
 7 
 8   <div id="el" style="position: absolute;">
 9     Javascript时钟实现动画
10   </div>
11   <script src="zepto.js" type="text/javascript"></script>
12   <script type="text/javascript">
13     var rAF = window.requestAnimationFrame ||
14     window.webkitRequestAnimationFrame ||
15     window.mozRequestAnimationFrame ||
16     window.oRequestAnimationFrame ||
17     window.msRequestAnimationFrame ||
18     function (callback) { window.setTimeout(callback, 1000 / 60); };
19     var pos = 0;
20     var final = 200;
21     var dir = 0;
22     var el = $('#el');
23     function upLeft() {
24       var left = parseInt(el.css('left')) || 0;
25       if (left >= final) dir = 1;
26       if (left <= pos) dir = 0;
27 
28       if (dir == 0) {
29         left++;
30         el.css('left', left + 'px');
31         rAF(upLeft);
32       } else {
33         left--;
34         el.css('left', left + 'px');
35         rAF(upLeft);
36       }
37     }
38     upLeft();
39   </script>
40 </body>
41 </html>

这个动画将会有不同的感觉:

动画效果相关,各位本身去感觉,代码各位能够本身调整。如此这个方法其实就是作javascript动画处理优化方案的

PS:尼玛,iScroll第一段就搞了这么久啊这么久

utils

而后iScroll将本身下面会用到的经常使用操做封装到了这个对象中——utils。

  1 var utils = (function () {
  2     var me = {};
  3 
  4     var _elementStyle = document.createElement('div').style;
  5     var _vendor = (function () {
  6         var vendors = ['t', 'webkitT', 'MozT', 'msT', 'OT'],
  7             transform,
  8             i = 0,
  9             l = vendors.length;
 10 
 11         for ( ; i < l; i++ ) {
 12             transform = vendors[i] + 'ransform';
 13             if ( transform in _elementStyle ) return vendors[i].substr(0, vendors[i].length-1);
 14         }
 15 
 16         return false;
 17     })();
 18 
 19     function _prefixStyle (style) {
 20         if ( _vendor === false ) return false;
 21         if ( _vendor === '' ) return style;
 22         return _vendor + style.charAt(0).toUpperCase() + style.substr(1);
 23     }
 24 
 25     me.getTime = Date.now || function getTime () { return new Date().getTime(); };
 26 
 27     me.extend = function (target, obj) {
 28         for ( var i in obj ) {
 29             target[i] = obj[i];
 30         }
 31     };
 32 
 33     me.addEvent = function (el, type, fn, capture) {
 34         el.addEventListener(type, fn, !!capture);
 35     };
 36 
 37     me.removeEvent = function (el, type, fn, capture) {
 38         el.removeEventListener(type, fn, !!capture);
 39     };
 40 
 41     me.momentum = function (current, start, time, lowerMargin, wrapperSize) {
 42         var distance = current - start,
 43             speed = Math.abs(distance) / time,
 44             destination,
 45             duration,
 46             deceleration = 0.0006;
 47 
 48         destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 );
 49         duration = speed / deceleration;
 50 
 51         if ( destination < lowerMargin ) {
 52             destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin;
 53             distance = Math.abs(destination - current);
 54             duration = distance / speed;
 55         } else if ( destination > 0 ) {
 56             destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0;
 57             distance = Math.abs(current) + destination;
 58             duration = distance / speed;
 59         }
 60 
 61         return {
 62             destination: Math.round(destination),
 63             duration: duration
 64         };
 65     };
 66 
 67     var _transform = _prefixStyle('transform');
 68 
 69     me.extend(me, {
 70         hasTransform: _transform !== false,
 71         hasPerspective: _prefixStyle('perspective') in _elementStyle,
 72         hasTouch: 'ontouchstart' in window,
 73         hasPointer: navigator.msPointerEnabled,
 74         hasTransition: _prefixStyle('transition') in _elementStyle
 75     });
 76 
 77     // This should find all Android browsers lower than build 535.19 (both stock browser and webview)
 78     me.isBadAndroid = /Android/.test(window.navigator.appVersion) && !(/Chrome\/\d/.test(window.navigator.appVersion));
 79 
 80     me.extend(me.style = {}, {
 81         transform: _transform,
 82         transitionTimingFunction: _prefixStyle('transitionTimingFunction'),
 83         transitionDuration: _prefixStyle('transitionDuration'),
 84         transitionDelay: _prefixStyle('transitionDelay'),
 85         transformOrigin: _prefixStyle('transformOrigin')
 86     });
 87 
 88     me.hasClass = function (e, c) {
 89         var re = new RegExp("(^|\\s)" + c + "(\\s|$)");
 90         return re.test(e.className);
 91     };
 92 
 93     me.addClass = function (e, c) {
 94         if ( me.hasClass(e, c) ) {
 95             return;
 96         }
 97 
 98         var newclass = e.className.split(' ');
 99         newclass.push(c);
100         e.className = newclass.join(' ');
101     };
102 
103     me.removeClass = function (e, c) {
104         if ( !me.hasClass(e, c) ) {
105             return;
106         }
107 
108         var re = new RegExp("(^|\\s)" + c + "(\\s|$)", 'g');
109         e.className = e.className.replace(re, ' ');
110     };
111 
112     me.offset = function (el) {
113         var left = -el.offsetLeft,
114             top = -el.offsetTop;
115 
116         // jshint -W084
117         while (el = el.offsetParent) {
118             left -= el.offsetLeft;
119             top -= el.offsetTop;
120         }
121         // jshint +W084
122 
123         return {
124             left: left,
125             top: top
126         };
127     };
128 
129     me.preventDefaultException = function (el, exceptions) {
130         for ( var i in exceptions ) {
131             if ( exceptions[i].test(el[i]) ) {
132                 return true;
133             }
134         }
135 
136         return false;
137     };
138 
139     me.extend(me.eventType = {}, {
140         touchstart: 1,
141         touchmove: 1,
142         touchend: 1,
143 
144         mousedown: 2,
145         mousemove: 2,
146         mouseup: 2,
147 
148         MSPointerDown: 3,
149         MSPointerMove: 3,
150         MSPointerUp: 3
151     });
152 
153     me.extend(me.ease = {}, {
154         quadratic: {
155             style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
156             fn: function (k) {
157                 return k * ( 2 - k );
158             }
159         },
160         circular: {
161             style: 'cubic-bezier(0.1, 0.57, 0.1, 1)',    // Not properly "circular" but this looks better, it should be (0.075, 0.82, 0.165, 1)
162             fn: function (k) {
163                 return Math.sqrt( 1 - ( --k * k ) );
164             }
165         },
166         back: {
167             style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
168             fn: function (k) {
169                 var b = 4;
170                 return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1;
171             }
172         },
173         bounce: {
174             style: '',
175             fn: function (k) {
176                 if ( ( k /= 1 ) < ( 1 / 2.75 ) ) {
177                     return 7.5625 * k * k;
178                 } else if ( k < ( 2 / 2.75 ) ) {
179                     return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75;
180                 } else if ( k < ( 2.5 / 2.75 ) ) {
181                     return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375;
182                 } else {
183                     return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;
184                 }
185             }
186         },
187         elastic: {
188             style: '',
189             fn: function (k) {
190                 var f = 0.22,
191                     e = 0.4;
192 
193                 if ( k === 0 ) { return 0; }
194                 if ( k == 1 ) { return 1; }
195 
196                 return ( e * Math.pow( 2, - 10 * k ) * Math.sin( ( k - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 );
197             }
198         }
199     });
200 
201     me.tap = function (e, eventName) {
202         var ev = document.createEvent('Event');
203         ev.initEvent(eventName, true, true);
204         ev.pageX = e.pageX;
205         ev.pageY = e.pageY;
206         e.target.dispatchEvent(ev);
207     };
208 
209     me.click = function (e) {
210         var target = e.target,
211             ev;
212 
213         if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA') {
214             ev = document.createEvent('MouseEvents');
215             ev.initMouseEvent('click', true, true, e.view, 1,
216                 target.screenX, target.screenY, target.clientX, target.clientY,
217                 e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
218                 0, null);
219 
220             ev._constructed = true;
221             target.dispatchEvent(ev);
222         }
223     };
224 
225     return me;
226 })();
View Code

兼容性检测

很烦的事情一而再再而三的在浏览器上面出现,好比咱们在chrome要定义动画参数得加上一个前缀webkit,而后ff要使用MozT,这个事情很烦,因此iScroll这段代码就在处理这个事情

 1 var _vendor = (function () {
 2     var vendors = ['t', 'webkitT', 'MozT', 'msT', 'OT'],
 3         transform,
 4         i = 0,
 5         l = vendors.length;
 6 
 7     for ( ; i < l; i++ ) {
 8         transform = vendors[i] + 'ransform';
 9         if ( transform in _elementStyle ) return vendors[i].substr(0, vendors[i].length-1);
10     }
11 
12     return false;
13 })();
14 
15 function _prefixStyle (style) {
16     if ( _vendor === false ) return false;
17     if ( _vendor === '' ) return style;
18     return _vendor + style.charAt(0).toUpperCase() + style.substr(1);
19 }

这里作了几个操做:

① 动态建立标签样式——_elementStyle

② 检测样式支持度,而且返回须要的前缀

③ 获取验证结果,好比在chrome下变会返回webkit-XXX

固然,这里要加前缀的样式,通常都与CSS3有关,而下面就会遇到是transform

getTime

me.getTime = Date.now || function getTime () { return new Date().getTime(); };
//获取当前时间戳

extend

最最简单的扩展对象的方法

1 me.extend = function (target, obj) {
2     for ( var i in obj ) {
3         target[i] = obj[i];
4     }
5 };

addEvent/removeEvent

事件注册相关,我在想,如果使用了zepto这个代码量会减小点么?

1 me.addEvent = function (el, type, fn, capture) {
2     el.addEventListener(type, fn, !!capture);
3 };
4 
5 me.removeEvent = function (el, type, fn, capture) {
6     el.removeEventListener(type, fn, !!capture);
7 };

momentum

这个方法比较重要,用于计算动画参数,会根据这个计算结果而决定动画运动效果,其实咱们昨天的demo也用到了相似的东西

 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 };

咱们来作一点解释,首先看看咱们的参数:

① 这个方法应该是在touchend时候使用,第一个参数为当前鼠标的位置

② 第二个参数是touchstart时候记录的坐标位置

③ 第三个参数为时间参数,即是开始触屏到离开时候所用时间(touchstart到touchend)

PS:这里咱们其实能够作一个猜想了,咱们有一次触屏的时间与距离,天然能够根据动力加速度计算出这次应该运动的时间与距离

④ 第四个参数是干神马的还不太明确,应该是控制边界位置的,这个就决定了咱们不能无限制的拖动wrapper

⑤ 第五个参数为容器的高度

而后咱们来以此读一读这里的代码:

① 得出这次拉动的距离distance/而后计算出此次拖动的速度(PS:我的以为这里操做很不错,我没有想到)

② 而后定义了一些其它参数,deceleration这个用于计算速度/距离的参数,而后两个就是要返回的距离以及时间了

PS:我想说他这里的计算最终位置的函数应该是物理里面的一个计算摩擦参数的公式,尼玛是什么我真的不知道了,还有平方来着......

③ 这里还有一个关键点就是distance有多是负值,这个会决定向上仍是向下运动

④ 通常状况这里就结束来了,而后下面if里面一大段计算是用于处理运动轨迹超出时候的距离与速度从新计算(反弹效果)

好了,这个函数比较关键,他主要返回了最后要去到的位置,已经到这个位置的时间,里面具体的实现咱们暂时不关系,后面这个必须理一理

检测与初始化

接下来作了一大段能力检测,好比:

① 是否支持CSS3动画相关(transform、transition)

② 是否支持touch事件

而后作了一些简单的初始化操做,这里新增了一个style对象,为他赋予了CSS动画相关的属性

③ 接下来是一些简单的样式操做,这样有个函数须要注意,他能够获取一个元素真正的位置信息

 1 me.offset = function (el) {
 2     var left = -el.offsetLeft,
 3         top = -el.offsetTop;
 4 
 5     // jshint -W084
 6     while (el = el.offsetParent) {
 7         left -= el.offsetLeft;
 8         top -= el.offsetTop;
 9     }
10     // jshint +W084
11 
12     return {
13         left: left,
14         top: top
15     };
16 };

④ 动画曲线

 1 me.extend(me.ease = {}, {
 2     quadratic: {
 3         style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
 4         fn: function (k) {
 5             return k * ( 2 - k );
 6         }
 7     },
 8     circular: {
 9         style: 'cubic-bezier(0.1, 0.57, 0.1, 1)',    // Not properly "circular" but this looks better, it should be (0.075, 0.82, 0.165, 1)
10         fn: function (k) {
11             return Math.sqrt( 1 - ( --k * k ) );
12         }
13     },
14     back: {
15         style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
16         fn: function (k) {
17             var b = 4;
18             return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1;
19         }
20     },
21     bounce: {
22         style: '',
23         fn: function (k) {
24             if ( ( k /= 1 ) < ( 1 / 2.75 ) ) {
25                 return 7.5625 * k * k;
26             } else if ( k < ( 2 / 2.75 ) ) {
27                 return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75;
28             } else if ( k < ( 2.5 / 2.75 ) ) {
29                 return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375;
30             } else {
31                 return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;
32             }
33         }
34     },
35     elastic: {
36         style: '',
37         fn: function (k) {
38             var f = 0.22,
39                 e = 0.4;
40 
41             if ( k === 0 ) { return 0; }
42             if ( k == 1 ) { return 1; }
43 
44             return ( e * Math.pow( 2, - 10 * k ) * Math.sin( ( k - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 );
45         }
46     }
47 });
View Code

这里定义了动画曲线供咱们选取,我通常使用linear......

PS:这个地方的代码,不明觉厉!!!

自定义点击事件

 1 me.tap = function (e, eventName) {
 2     var ev = document.createEvent('Event');
 3     ev.initEvent(eventName, true, true);
 4     ev.pageX = e.pageX;
 5     ev.pageY = e.pageY;
 6     e.target.dispatchEvent(ev);
 7 };
 8 
 9 me.click = function (e) {
10     var target = e.target,
11         ev;
12 
13     if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA') {
14         ev = document.createEvent('MouseEvents');
15         ev.initMouseEvent('click', true, true, e.view, 1,
16             target.screenX, target.screenY, target.clientX, target.clientY,
17             e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
18             0, null);
19 
20         ev._constructed = true;
21         target.dispatchEvent(ev);
22     }
23 };

iScroll这里干了一件坏事,本身定义了tap以及click,意思是他能够触发绑定到dom上的tap或者click事件

而后整个util便结束了,其中momentum方法非常关键,接下来咱们跟着程序流程走了

构造函数

构造函数是iScroll的入口,咱们来详细读一读:

wrapper/scroller

为咱们的外层结构,再里面一点就是拖动元素了,iscroll的处理是认为wrapper下第一个元素就是可拖动元素,我这里任务不妥......

 

this.scroller = this.wrapper.children[0];

咱们拖动的就是这个scroller了,我为何说这样不妥呢?由于我若是如今又一个弹出层想使用iScroll的话,如果我弹出层有了wrapper了,我想本身往里面装DOM

而个人DOM搞很差有几个兄弟节点,这个时候我确定不想本身再包裹一层的,因此iScroll这里的scroller我以为系统构建比较合理(固然这只是我的认为)

下面还缓存了下当前scroll元素的style

this.scrollerStyle = this.scroller.style;    

初始化参数

iScroll固然本身会初始化一些默认属性了:

 1 this.options = {
 2 
 3     resizeIndicator: true,
 4 
 5     mouseWheelSpeed: 20,
 6 
 7     snapThreshold: 0.334,
 8 
 9 // INSERT POINT: OPTIONS 
10 
11     startX: 0,
12     startY: 0,
13     scrollY: true,
14     directionLockThreshold: 5,
15     momentum: true,
16 
17     bounce: true,
18     bounceTime: 600,
19     bounceEasing: '',
20 
21     preventDefault: true,
22     preventDefaultException: { tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/ },
23 
24     HWCompositing: true,
25     useTransition: true,
26     useTransform: true
27 };

如果咱们传了相关属性会被复写的(这里上面定义了extend,却没有使用):

1 for ( var i in options ) {
2     this.options[i] = options[i];
3 }

能力检测

而后下面一大片结果基本作了一些能力检测的功能,而后将检测结果保存,由于我暂时只关心纵向滑动,因此一些地方便不予关心了

1 this.x = 0;
2 this.y = 0;
3 this.directionX = 0;
4 this.directionY = 0;
5 this._events = {};

这一坨东西仍是要关注的,方向和初始值

初始化_init

上面一些默认属性定义结束便进入真正的初始化阶段,

 1 _init: function () {
 2     this._initEvents();
 3 
 4     if ( this.options.scrollbars || this.options.indicators ) {
 5         this._initIndicators();
 6     }
 7 
 8     if ( this.options.mouseWheel ) {
 9         this._initWheel();
10     }
11 
12     if ( this.options.snap ) {
13         this._initSnap();
14     }
15 
16     if ( this.options.keyBindings ) {
17         this._initKeys();
18     }
19 // INSERT POINT: _init
20 },

代码很清晰,我如今的需求关注_initEvents与_initIndicators就行了,其它暂时能够无论,关键点即是事件绑定了

 

_initEvents

 1 _initEvents: function (remove) {
 2     var eventType = remove ? utils.removeEvent : utils.addEvent,
 3         target = this.options.bindToWrapper ? this.wrapper : window;
 4 
 5     eventType(window, 'orientationchange', this);
 6     eventType(window, 'resize', this);
 7 
 8     if ( this.options.click ) {
 9         eventType(this.wrapper, 'click', this, true);
10     }
11 
12     if ( !this.options.disableMouse ) {
13         eventType(this.wrapper, 'mousedown', this);
14         eventType(target, 'mousemove', this);
15         eventType(target, 'mousecancel', this);
16         eventType(target, 'mouseup', this);
17     }
18 
19     if ( utils.hasPointer && !this.options.disablePointer ) {
20         eventType(this.wrapper, 'MSPointerDown', this);
21         eventType(target, 'MSPointerMove', this);
22         eventType(target, 'MSPointerCancel', this);
23         eventType(target, 'MSPointerUp', this);
24     }
25 
26     if ( utils.hasTouch && !this.options.disableTouch ) {
27         eventType(this.wrapper, 'touchstart', this);
28         eventType(target, 'touchmove', this);
29         eventType(target, 'touchcancel', this);
30         eventType(target, 'touchend', this);
31     }
32 
33     eventType(this.scroller, 'transitionend', this);
34     eventType(this.scroller, 'webkitTransitionEnd', this);
35     eventType(this.scroller, 'oTransitionEnd', this);
36     eventType(this.scroller, 'MSTransitionEnd', this);
37 },

这段代码,是整个iScroll的核心,整个入口函数其实在这里,咱们暂时的关注点又在这里:

1 if ( utils.hasTouch && !this.options.disableTouch ) {
2     eventType(this.wrapper, 'touchstart', this);
3     eventType(target, 'touchmove', this);
4     eventType(target, 'touchcancel', this);
5     eventType(target, 'touchend', this);
6 }

PS:这里有一点让我比较疑惑的就是这里传递进去的fn是一个对象,而不是函数,看来我事件机制一块仍然不到家:

而后进入咱们的touch事件,反正如今touchstart便会进入咱们的start回调函数

touchStart

 1 _start: function (e) {
 2     // React to left mouse button only
 3     if ( utils.eventType[e.type] != 1 ) {
 4         if ( e.button !== 0 ) {
 5             return;
 6         }
 7     }
 8 
 9     if ( !this.enabled || (this.initiated && utils.eventType[e.type] !== this.initiated) ) {
10         return;
11     }
12 
13     if ( this.options.preventDefault && !utils.isBadAndroid && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) {
14         e.preventDefault();
15     }
16 
17     var point = e.touches ? e.touches[0] : e,
18         pos;
19 
20     this.initiated    = utils.eventType[e.type];
21     this.moved        = false;
22     this.distX        = 0;
23     this.distY        = 0;
24     this.directionX = 0;
25     this.directionY = 0;
26     this.directionLocked = 0;
27 
28     this._transitionTime();
29 
30     this.startTime = utils.getTime();
31 
32     if ( this.options.useTransition && this.isInTransition ) {
33         this.isInTransition = false;
34         pos = this.getComputedPosition();
35         this._translate(Math.round(pos.x), Math.round(pos.y));
36         this._execEvent('scrollEnd');
37     } else if ( !this.options.useTransition && this.isAnimating ) {
38         this.isAnimating = false;
39         this._execEvent('scrollEnd');
40     }
41 
42     this.startX    = this.x;
43     this.startY    = this.y;
44     this.absStartX = this.x;
45     this.absStartY = this.y;
46     this.pointX    = point.pageX;
47     this.pointY    = point.pageY;
48 
49     this._execEvent('beforeScrollStart');
50 }

前面作了一系列的兼容性处理,而后记录了一些数据便结束了

结语

今天有点晚了,我也暂时结束了,明天还要上班呢,下次详细研究下touch事件的几个阶段干的事情以及滚动条的实现