随笔- 260  评论- 4227  文章- 3 

【iScroll源码学习00】模拟iScroll

前言

相信对移动端有了解的朋友对iScroll这个库非常熟悉吧,今天我们就来说下我们移动页面的iScroll化

iScroll是我们必学框架之一,我们这次先根据iScroll功能自己实现其功能,然后再学习iScroll源码

下面先给出iScroll官方的例子和源码,要看效果的朋友自己去看吧:https://github.com/cubiq/iscroll

本人能力有限,文中有误请提出

viewport

在移动端新出了一个属性叫做“viewport”,这个便是我们手机上的虚拟视口(viewport),也就是视觉窗口,显示区域

移动设备的显示区域比电脑小得多(但也方便得多),为了让手机显示的更加友好,Apple提供了一个方法:在浏览器定义了viewport meta标签

他的作用就是创建一个虚拟窗口,这个虚拟窗口接近桌面浏览器(980px),事实上viewport就是用以放大缩小网页内容

<meta name=”viewport” 
content=”width=device-width, initial-scale=1, maximum-scale=1″>

width:控制 viewport 的大小,可以指定的一个值,如果 600,或者特殊的值,如 device-width 为设备的宽度(单位为缩放为 100% 时的 CSS 的像素)。
height:和 width 相对应,指定高度。
initial-scale:初始缩放比例,也即是当页面第一次 load 的时候缩放比例。
maximum-scale:允许用户缩放到的最大比例。
minimum-scale:允许用户缩放到的最小比例。
user-scalable:用户是否可以手动缩放

visual/layout viewport

(此处引用——原文出处: quirksmode   译文出处: Zhao Yuhao

想象我们有个房间,我们可以控制房间大小,现在我们站在他窗户面前,正对着窗户的墙壁涂满了壁画,我们走到窗口一米的位置往房间看(假设房子很大)

我们能看到整个壁画,但是有点小,于是我们缩小房子就能看清细节了,这里的窗户就是visual viewport 墙壁就是layout viewport

对于css布局,特别是用宽度百分比做排版时候,比率是按照layout viewport计算,也就是说一个div相对宽度50%,用户在手机浏览器放大缩小

div宽度不会一直显示相对窗口50%,整个div可能铺满窗口小到看不到

我们这里的viewport就相当于放大和缩小房间,找到一个合适的平衡点,让我们的网页在手机上更友好的显示

① 假如我们现在又个简单的页面,不给div设置宽度(默认是layout100%——980px),所以显示效果为:

② 用户通过放大网页比例,缩小visual viewport的值,相对而言用户就能看清楚div的内容了,但是layout viewport本身未发现变化(所以可能出现滚动条)

③ 这个时候上文中的device-width就派上了用场,他可以将layout viewport的像素设置为设备的像素,这样的话:

visual viewport=layout viewport=screen width,这个体验就比较好了

device-width

以上知识点暂时到这,这里我们补充几个知识点:

① 宽度问题:

layout viewport 的长宽 (document.documentElement.clientWidth / document.documentElement.clientHeight)

visual viewport 的长宽 (window.innerWidth / window.innerHeight)

② 设备像素

screen.width/height

③ Media queries,这个是html5新增特性,可以根据device-width(设备宽度,screen width)来确定显示不同的CSS

1. visual viewport 宽度 : 默认980 实际大小与缩放比例相关,可以通过meta的viewport属性修改
2. layout viewport 宽度 : 980 
3. screen.width         :320

我们这里来重新理解下device-width这个属性,这里提供一段代码,两个截图:

 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     <meta name="viewport" content="width=device-width">
 6 </head>
 7 <body>
 8   方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨
 9 </body>
10 </html>
View Code
<meta name="viewport" content="width=100">
<!-- 这里以此给width赋值:① 默认情况/② device-width/③ 100 ->

为何出现iScroll

fixed与噩梦

最初的ios与android并不支持fixed属性,因为我们的手机有一个叫viewport的东西这个大家都知道了,fixed位置是相对整个页面的固定位置

页面中的页面没什么变化,只不过在viewport下变大了,而且我们移动的是viewport,网页并未跟着滚动,于是我们移动的事实上是viewport,

而我们viewport移动并不会让我们fixed元素跟着变化,因为他是相对于手机屏幕的,所以就不支持了,反正后面这个问题被修复了

但是据我的经验来说,就是现在ios6、7或者android高版本fixed仍然不是那么好使,移动端的fixed就跟ie7的float似的,让人想哭

特别是当你点击文本框时候看到键盘上来了,页面错位了,一股想扔手机之情油然而生

加之想要用户自动升级手机浏览器什么的仍然不现实,所以iScroll诞生了,这是iScroll诞生的主要原因(我是这么认为的)

overflow: auto

既然fixed不好使,那么就头尾固定,中间body部分使用overflow属性吧,但是可恨的是overflow属性仍然不好使!!!

我们这里来做一个demo:

 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     <meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
 6     <style type="text/css">
 7     #header { width: 100%; height: 50px; position: absolute; top: 0; left: 0; }
 8     #footer { width: 100%; height: 50px; position: absolute; bottom: 0; left: 0; }
 9     #body {  height: 180px; margin: 60px 0; overflow: auto; }
10     div { border: 1px solid black; }
11     </style>
12 </head>
13 <body>
14 <div id="header">header</div>
15 <div id="body">方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方
16 <input type="text" />
17 法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨
18 
19 方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方
20 <input type="text" />
21 法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨
22 
23 </div>
24 <div id="footer">footer</div>
25   
26 </body>
27 </html>
View Code

其实这个属性也是可以实现三行布局的基本功能的,但是有以下几个问题:

① 没有滚动条

② 滚动不顺畅

③ 手机浏览器支持良莠不齐

但是这些缺点也不能掩盖他最大的优点:原生性!!!原生的就是最好的,如果哪天这个属性升级的话,前途就好了

页面切换动画

要伪装APP,页面切换动画必不可少,但是如果中间部分不固定的话就会碰到另外一个令人头疼的问题:

长短页切换问题,想象下几个长短不一的页面切换会有多丑呢???

PS:这也是我现在遇到的问题

基于以上原因,所以出现了iScroll这样的实用库,当然,以上只是个人猜想......

实现iScroll功能

基本dom结构

以上扯了那么多,与本文的最初目的关系其实不大,我们的主要目的还是得先实现一个简单的iScroll功能才行(依赖zepto)

 1 <html xmlns="http://www.w3.org/1999/xhtml">
 2 <head>
 3   <title></title>
 4   <meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
 5   <style type="text/css">
 6     body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, textarea, p, blockquote, th, td, hr, button, article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { margin: 0; padding: 0; }
 7     body { font: normal 14px/1.5 "Arial" , "Lucida Grande" ,Verdana, "Microsoft YaHei" , "hei"; -webkit-font-smoothing: antialiased; color: #000; background: #f5f5f5; }
 8     header { position: absolute; top: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
 9     footer { position: absolute; bottom: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
10     h1 { display: block; font-size: 2em; font-weight: bold; font-weight: 500; text-align: center; color: White; }
11     #body { background: #fff; border: 1px solid #cfcfcf; width: 96%; height: 100%; margin: 50px auto; padding: 4px; }
12   </style>
13 </head>
14 <body>
15   <header id="header">
16     <h1>
17       Header</h1>
18   </header>
19   <div id="body">
20     body
21   </div>
22   <footer>
23     <h1>
24       Footer</h1>
25   </footer>
26 </body>
27 </html>

我们根据iScroll的动作先写下以下代码:

  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   <meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
  6   <style type="text/css">
  7     body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, textarea, p, blockquote, th, td, hr, button, article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { margin: 0; padding: 0; }
  8     body { font: normal 14px/1.5 "Arial" , "Lucida Grande" ,Verdana, "Microsoft YaHei" , "hei"; -webkit-font-smoothing: antialiased; color: #000; background: #ccc; }
  9     header { position: absolute; top: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
 10     footer { position: absolute; bottom: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
 11     h1 { display: block; font-size: 2em; font-weight: bold; font-weight: 500; text-align: center; color: White; }
 12     
 13     #body { position: absolute; top: 50px; bottom: 50px; background: #fff; width: 100%; overflow: hidden; }
 14     #wrapper { width: 100%; }
 15     
 16     
 17     #wrapper li { padding: 0 10px; height: 40px; line-height: 40px; border-bottom: 1px solid #ccc; border-top: 1px solid #fff; background-color: #fafafa; font-size: 14px; }
 18   </style>
 19 </head>
 20 <body>
 21   <header id="header">
 22     <h1>
 23       Header</h1>
 24   </header>
 25   <div id="body">
 26     <div id="wrapper">
 27       body
 28       <ul>
 29         <li>Pretty row 1</li>
 30         <li>Pretty row 2</li>
 31         <li>Pretty row 3</li>
 32         <li>Pretty row 4</li>
 33         <li>
 34           <input type="text"></li>
 35         <li>Pretty row 6</li>
 36         <li>Pretty row 7</li>
 37         <li>Pretty row 8</li>
 38         <li>
 39           <input type="checkbox"></li>
 40         <li>Pretty row 10</li>
 41         <li>Pretty row 11</li>
 42         <li>Pretty row 12</li>
 43         <li>
 44           <input type="radio"></li>
 45         <li>Pretty row 14</li>
 46         <li>Pretty row 15</li>
 47         <li>Pretty row 16</li>
 48         <li>
 49           <textarea></textarea></li>
 50         <li>Pretty row 18</li>
 51         <li>Pretty row 19</li>
 52         <li>Pretty row 20</li>
 53         <li>
 54           <select>
 55             <option>option</option>
 56           </select></li>
 57         <li>Pretty row 22</li>
 58         <li>Pretty row 23</li>
 59         <li>Pretty row 24</li>
 60       </ul>
 61       <hr />
 62       <ul>
 63         <li>Pretty row 25</li>
 64         <li>Pretty row 26</li>
 65         <li>Pretty row 27</li>
 66         <li>Pretty row 28</li>
 67         <li>Pretty row 29</li>
 68         <li>Pretty row 30</li>
 69         <li>Pretty row 31</li>
 70         <li>Pretty row 32</li>
 71         <li>Pretty row 33</li>
 72         <li>Pretty row 34</li>
 73         <li>Pretty row 35</li>
 74         <li>Pretty row 36</li>
 75         <li>Pretty row 37</li>
 76         <li>Pretty row 38</li>
 77         <li>Pretty row 39</li>
 78         <li>Pretty row 40</li>
 79         <li>Pretty row 41</li>
 80         <li>Pretty row 42</li>
 81         <li>Pretty row 43</li>
 82         <li>Pretty row 44</li>
 83         <li>Pretty row 45</li>
 84         <li>Pretty row 46</li>
 85         <li>Pretty row 47</li>
 86         <li>Pretty row 48</li>
 87         <li>Pretty row 49</li>
 88         <li>Pretty row 50</li>
 89       </ul>
 90     </div>
 91   </div>
 92   <footer>
 93     <h1>
 94       Footer</h1>
 95   </footer>
 96   <script src="../../zepto/zepto-1.0/src/zepto.js" type="text/javascript"></script>
 97   <script type="text/javascript">
 98     var Scroll = function (opts) {
 99       opts = opts || {};
100        //检测设备事件支持,确定使用鼠标事件或者touch事件
101         this._checkEventCompatibility();
102         this._setBaseParam(opts);
103         this._initScrollBar();
104         //        this._addEvent();
105     };
106 
107     Scroll.prototype = {
108       constructor: Scroll,
109       //检测设备事件兼容
110       _checkEventCompatibility: function () {
111         var isTouch = 'ontouchstart' in document.documentElement;
112         this.start = isTouch ? 'touchstart' : 'mousedown';
113         this.move = isTouch ? 'touchmove' : 'mousemove';
114         this.end = isTouch ? 'touchend' : 'mouseup';
115         this.startFn;
116         this.moveFn;
117         this.endFn;
118       },
119       //基本参数设置
120       _setBaseParam: function (opts) {
121         this.timeGap = 0; //时间间隔
122         this.touchTime = 0; //开始时间
123         this.isMoveing = false; //是否正在移动
124         this.moveState = 'up'; //移动状态,up right down left
125         this.oTop = 0; //拖动前的top值
126         this.curTop = 0; //当前容器top
127         this.mouseY = 0; //鼠标第一次点下时相对父容器的位置
128         this.animateParam = opts.animateParam || [10, 8, 6, 5, 4, 3, 2, 1, 0, 0, 0]; //动画参数
129         this.cooling = true; //是否处于冷却时间
130         this.steplen = 25; //动画步长
131 
132         this.wrapper = opts.wrapper || $('body');
133         this.dragEl = opts.body;
134         this.wrapper.css({ 'position': 'absolute', 'overflow': 'hidden' });
135         this.dragEl.css('position', 'absolute');
136         this.wrapper.append(this.dragEl);
137       },
138       _initScrollBar: function () {
139             if (!this.dragHeight) {
140                 this.dragHeight = this.dragEl.offset().height; //拖动元素高度
141                 this.wrapperHeight = this.wrapper.offset().height;
142             }
143             //滚动条缩放比例
144             this.scrollProportion = this.wrapperHeight / this.dragHeight;
145             this.isNeedScrollBar = true;
146             //该种情况无需滚动条
147             if (this.scrollProportion >= 1) {
148                 this.isNeedScrollBar = false; ;
149                 return false;
150             }
151             //滚动条
152             this.scrollBar = $('<div style="background-color: rgba(0, 0, 0, 0.498039);border: 1px solid rgba(255, 255, 255, 0.901961); width: 5px; border-radius: 3px;  position: absolute; right: 1px; opacity: 0.2;  "></div>');
153             this.wrapper.append(this.scrollBar);
154             this.scrollHeight = parseInt(this.scrollProportion * this.wrapperHeight);
155             this.scrollBar.css('height', this.scrollHeight);
156         }
157 
158     };
159     new Scroll({ wrapper: $('#body'), body: $('#wrapper') }); 
160   </script>
161 </body>
162 </html>
View Code

http://sandbox.runjs.cn/show/oztjkadg(最好使用手机访问)

事件绑定

样式出来了,我们现在就该注册事件了,支持touch就是要touch,否则就是要mouse事件了:

  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   <meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
  6   <style type="text/css">
  7     body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, textarea, p, blockquote, th, td, hr, button, article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { margin: 0; padding: 0; }
  8     body { font: normal 14px/1.5 "Arial" , "Lucida Grande" ,Verdana, "Microsoft YaHei" , "hei"; -webkit-font-smoothing: antialiased; color: #000; background: #ccc; }
  9     header { position: absolute; top: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
 10     footer { position: absolute; bottom: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
 11     h1 { display: block; font-size: 2em; font-weight: bold; font-weight: 500; text-align: center; color: White; }
 12     
 13     #body { position: absolute; top: 50px; bottom: 50px; background: #fff; width: 100%; overflow: hidden; }
 14     #wrapper { width: 100%; }
 15     
 16     
 17     #wrapper li { padding: 0 10px; height: 40px; line-height: 40px; border-bottom: 1px solid #ccc; border-top: 1px solid #fff; background-color: #fafafa; font-size: 14px; }
 18   </style>
 19     <script id="others_zepto_10rc1" type="text/javascript" class="library" src="/js/sandbox/other/zepto.min.js"></script>
 20 </head>
 21 <body>
 22   <header id="header">
 23     <h1>
 24       Header</h1>
 25   </header>
 26   <div id="body">
 27     <div id="wrapper">
 28       body
 29       <ul>
 30         <li>Pretty row 1</li>
 31         <li>Pretty row 2</li>
 32         <li>Pretty row 3</li>
 33         <li>Pretty row 4</li>
 34         <li>
 35           <input type="text"></li>
 36         <li>Pretty row 6</li>
 37         <li>Pretty row 7</li>
 38         <li>Pretty row 8</li>
 39         <li>
 40           <input type="checkbox"></li>
 41         <li>Pretty row 10</li>
 42         <li>Pretty row 11</li>
 43         <li>Pretty row 12</li>
 44         <li>
 45           <input type="radio"></li>
 46         <li>Pretty row 14</li>
 47         <li>Pretty row 15</li>
 48         <li>Pretty row 16</li>
 49         <li>
 50           <textarea></textarea></li>
 51         <li>Pretty row 18</li>
 52         <li>Pretty row 19</li>
 53         <li>Pretty row 20</li>
 54         <li>
 55           <select>
 56             <option>option</option>
 57           </select></li>
 58       </ul>
 59       <hr />
 60       <ul>
 61         
 62         <li>Pretty row 41</li>
 63         <li>Pretty row 42</li>
 64         <li>Pretty row 43</li>
 65         <li>Pretty row 44</li>
 66         <li>Pretty row 45</li>
 67         <li>Pretty row 46</li>
 68         <li>Pretty row 47</li>
 69         <li>Pretty row 48</li>
 70         <li>Pretty row 49</li>
 71         <li>Pretty row 50</li>
 72       </ul>
 73     </div>
 74   </div>
 75   <footer>
 76     <h1>
 77       Footer</h1>
 78   </footer>
 79   <script src="zepto.js" type="text/javascript"></script>
 80   <script type="text/javascript">
 81     var Scroll = function (opts) {
 82       opts = opts || {};
 83       //检测设备事件支持,确定使用鼠标事件或者touch事件
 84       this._checkEventCompatibility();
 85       this._setBaseParam(opts);
 86       this._addEvent();
 87 
 88       this._initScrollBar();
 89     };
 90 
 91     Scroll.prototype = {
 92       constructor: Scroll,
 93       //检测设备事件兼容
 94       _checkEventCompatibility: function () {
 95         var isTouch = 'ontouchstart' in document.documentElement;
 96 
 97         this.start = isTouch ? 'touchstart' : 'mousedown';
 98         this.move = isTouch ? 'touchmove' : 'mousemove';
 99         this.end = isTouch ? 'touchend' : 'mouseup';
100         this.startFn;
101         this.moveFn;
102         this.endFn;
103       },
104       //基本参数设置
105       _setBaseParam: function (opts) {
106         this.timeGap = 0; //时间间隔
107         this.touchTime = 0; //开始时间
108         this.isMoveing = false; //是否正在移动
109         this.moveState = 'up'; //移动状态,up right down left
110         this.oTop = 0; //拖动前的top值
111         this.curTop = 0; //当前容器top
112         this.mouseY = 0; //鼠标第一次点下时相对父容器的位置
113         this.animateParam = opts.animateParam || [10, 8, 6, 5, 4, 3, 2, 1, 0, 0, 0]; //动画参数
114         this.cooling = true; //是否处于冷却时间
115         this.steplen = 25; //动画步长
116 
117         this.wrapper = opts.wrapper || $('body');
118         this.dragEl = opts.body;
119         this.wrapper.css({ 'position': 'absolute', 'overflow': 'hidden' });
120         this.dragEl.css('position', 'absolute');
121         this.wrapper.append(this.dragEl);
122       },
123       _initScrollBar: function () {
124         if (!this.dragHeight) {
125           this.dragHeight = this.dragEl.offset().height; //拖动元素高度
126           this.wrapperHeight = this.wrapper.offset().height;
127         }
128         //滚动条缩放比例
129         this.scrollProportion = this.wrapperHeight / this.dragHeight;
130         this.isNeedScrollBar = true;
131         //该种情况无需滚动条
132         if (this.scrollProportion >= 1) {
133           this.isNeedScrollBar = false; ;
134           return false;
135         }
136         //滚动条
137         this.scrollBar = $('<div style="background-color: rgba(0, 0, 0, 0.498039);border: 1px solid rgba(255, 255, 255, 0.901961); width: 5px; border-radius: 3px;  position: absolute; right: 1px; opacity: 0.2;  "></div>');
138         this.wrapper.append(this.scrollBar);
139         this.scrollHeight = parseInt(this.scrollProportion * this.wrapperHeight);
140         this.scrollBar.css('height', this.scrollHeight);
141       },
142       _setScrollTop: function (top, duration) {
143         //滚动条高度
144         if (this.isNeedScrollBar) {
145           top = this._getResetData(top).sTop;
146           top = top < 0 ? (top + 10) : top;
147 
148           var scrollTop = top * (-1);
149           if (typeof duration == 'number') {
150             var _top = parseInt(scrollTop * this.scrollProportion) + 'px';
151             this.scrollBar.animate({
152               top: _top,
153               right: '1px'
154             }, duration, 'linear');
155 
156           } else {
157             this.scrollBar.css('top', parseInt(scrollTop * this.scrollProportion) + 'px');
158           }
159           this.scrollBar.css('opacity', '0.8');
160         }
161       },
162       _hideScroll: function () {
163         if (this.isNeedScrollBar) {
164           this.scrollBar.animate({ 'opacity': '0.2' });
165         }
166       },
167       _addEvent: function () {
168         var scope = this;
169         this.startFn = function (e) {
170           scope._touchStart.call(scope, e);
171         };
172         this.moveFn = function (e) {
173           scope._touchMove.call(scope, e);
174         };
175         this.endFn = function (e) {
176           scope._touchEnd.call(scope, e);
177         };
178         this.dragEl[0].addEventListener(this.start, this.startFn, false);
179         document.addEventListener(this.move, this.moveFn, false);
180         document.addEventListener(this.end, this.endFn, false);
181       },
182       removeEvent: function () {
183         this.dragEl[0].removeEventListener(this.start, this.startFn);
184         document.removeEventListener(this.move, this.moveFn);
185         document.removeEventListener(this.end, this.endFn);
186       },
187       _touchStart: function (e) {
188         var scope = this;
189         if (this.isMoveing) { e.preventDefault(); return false; }
190         //非运动情况关闭冷却时间
191         this.cooling = false;
192         this.touchTime = e.timeStamp;
193         pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e);
194         var top = parseFloat(this.dragEl.css('top')) || 0;
195         this.mouseY = pos.top - top;
196       },
197       _touchMove: function (e) {
198         if (this.cooling) { e.preventDefault(); return false; }
199 
200         this.isMoveing = true;
201 
202         e.preventDefault();
203         var pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e);
204 
205         //防止点击时候跳动
206         if (Math.abs((pos.top - this.mouseY) - this.curTop) < 10) { e.preventDefault(); return false; }
207 
208         //先获取相对容器的位置,在将两个鼠标位置相减
209         this.curTop = pos.top - this.mouseY;
210 
211         var resetData = this._getResetData(this.curTop);
212         if (resetData.needReset) {
213           this.curTop = this._resetEdge(this.curTop);
214         }
215 
216         this.dragEl.css('top', this.curTop + 'px');
217         this._setScrollTop(this.curTop);
218         e.preventDefault();
219 
220       },
221       _touchEnd: function (e) {
222         if (this.cooling) { e.preventDefault(); return false; }
223         if (Math.abs(this.oTop - this.curTop) < 10) { e.preventDefault(); return false; }
224         //一次动作结束,开启冷却时间
225         this.cooling = true;
226         var scope = this;
227         this.timeGap = e.timeStamp - this.touchTime;
228         var flag = this.oTop < this.curTop ? 1 : -1; //判断是向上还是向下滚动
229         this.moveState = flag > 0 ? 'up' : 'down';
230 
231         var step = parseInt(this.timeGap / 10 - 10);
232         step = step > 0 ? step : 0;
233         var speed = this.animateParam[step] || 0;
234         var increment = speed * this.steplen * flag;
235         var top = this.curTop;
236         top += increment;
237 
238         var resetData = this._getResetData(top);
239         if (resetData.needReset) {
240           top = this._resetEdge(top);
241           speed = 0;
242         }
243 
244         //!!!此处动画可能导致数据不同步,后期改造需要加入冷却时间
245         if (this.oTop != this.curTop && this.curTop != top) {
246           var duration = 100 + (speed * 20);
247           top += increment;
248           this.dragEl.animate({
249             top: top + 'px'
250           }, duration, 'linear', function () {
251             scope.reset.call(scope, top);
252 
253           });
254           this._setScrollTop(top, duration);
255         } else {
256           this.isMoveing = false;
257           this.oTop = top;
258           this.reset(top);
259           this.cooling = false; //关闭冷却时间
260         }
261         this._hideScroll();
262         e.preventDefault();
263       },
264       _resetEdge: function (top) {
265         var h1 = parseInt(this.wrapperHeight / 3);
266         var h2 = parseInt(this.dragHeight * (-1) + this.wrapperHeight * (2 / 3));
267         if (top > 0 && top > h1) top = h1;
268         if (top < 0 && top < h2) top = h2;
269         return top;
270       },
271       _getResetData: function (top) {
272         var needReset = false;
273         var sTop = top;
274         if (top < (-1) * (this.dragHeight - this.wrapperHeight)) { top = (-1) * (this.dragHeight - this.wrapperHeight); needReset = true; }
275         if (top > 0) { top = 0; needReset = true; }
276 
277         return {
278           top: top,
279           sTop: sTop,
280           needReset: needReset
281         };
282       },
283       //超出限制后位置还原
284       reset: function (top) {
285         var scope = this;
286         var needReset = this._getResetData(top).needReset;
287         var top = this._getResetData(top).top;
288 
289         if (needReset) {
290           scope.dragEl.animate({
291             top: top + 'px'
292           }, 50, 'linear', function () {
293             scope._reset(top);
294             scope._setScrollTop(top);
295 
296           });
297         } else {
298           scope._reset(top);
299         }
300       },
301       _reset: function (top) {
302         this.oTop = top;
303         this.curTop = top;
304         this.isMoveing = false;
305         this.cooling = false; //关闭冷却时间
306       },
307       //获取鼠标信息
308       getMousePos: function (event) {
309         var top, left;
310         top = Math.max(document.body.scrollTop, document.documentElement.scrollTop);
311         left = Math.max(document.body.scrollLeft, document.documentElement.scrollLeft);
312         return {
313           top: top + event.clientY,
314           left: left + event.clientX
315         };
316       }
317     };
318     new Scroll({ wrapper: $('#body'), body: $('#wrapper') }); 
319   </script>
320 </body>
321 </html>
View Code

这个是我们第一步形成的代码,他具有以下问题待解决:

① 由于是CSS3实现的动画,不能保存状态,所以我们再次点击时候不能停止动画

② 未使用CSS3的transform,所以整个功能暂不支持3D加速

③ 由于touch时候的e.preventDefault,所以其中的文本框等在手机上不能获取焦点

以上焦点便是我们接下来需要解决的地方,上面提出了三大问题,我们这里来一一解决

硬件加速

各位看到上面实现动画的方法是通过变化元素的Top实现的,这样做原来有一个好处就是可以向下兼容,但是对于移动端来说意义不大

事实上这里的top实现动画变为translate实现动画更为舒服,原因是手机对CSS3动画做了处理,可以开启硬件加速

我们可以在浏览器中用css开启硬件加速,使GPU (Graphics Processing Unit) 发挥功能,从而提升性能

CSS animations, transforms 以及 transitions 不会自动开启GPU加速,而是由浏览器的缓慢的软件渲染引擎来执行。

.cube {
   -webkit-transform: translate3d(250px,250px,250px)
   rotate3d(250px,250px,250px,-120deg)
   scale3d(0.5, 0.5, 0.5);
}

以上代码便会开启硬件加速,所以我们这里的对应关系是这样的:

top=>translate3d(0, 0, 0)

加速是好的,滥用可能引起性能问题,而且ios下动画可能产生抖动现象,这个各位一定要注意,于是通过这个,我们修改我们的代码:

http://sandbox.runjs.cn/show/wqw1lpcl(请用webkit手机对比)

  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   <meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
  6   <style type="text/css">
  7     body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, textarea, p, blockquote, th, td, hr, button, article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { margin: 0; padding: 0; }
  8     body { font: normal 14px/1.5 "Arial" , "Lucida Grande" ,Verdana, "Microsoft YaHei" , "hei"; -webkit-font-smoothing: antialiased; color: #000; background: #ccc; }
  9     header { position: absolute; top: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
 10     footer { position: absolute; bottom: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
 11     h1 { display: block; font-size: 2em; font-weight: bold; font-weight: 500; text-align: center; color: White; }
 12     
 13     #body { position: absolute; top: 50px; bottom: 50px; background: #fff; width: 100%; overflow: hidden; }
 14     #wrapper { width: 100%; }
 15     
 16     
 17     #wrapper li { padding: 0 10px; height: 40px; line-height: 40px; border-bottom: 1px solid #ccc; border-top: 1px solid #fff; background-color: #fafafa; font-size: 14px; }
 18   </style>
 19     <script id="others_zepto_10rc1" type="text/javascript" class="library" src="/js/sandbox/other/zepto.min.js"></script>
 20 </head>
 21 <body>
 22   <header id="header">
 23     <h1>
 24       Header</h1>
 25   </header>
 26   <div id="body">
 27     <div id="wrapper">
 28       body
 29       <ul>
 30         <li>Pretty row 1</li>
 31         <li>Pretty row 2</li>
 32         <li>Pretty row 3</li>
 33         <li>Pretty row 4</li>
 34         <li>
 35           <input type="text"></li>
 36         <li>Pretty row 6</li>
 37         <li>Pretty row 7</li>
 38         <li>Pretty row 8</li>
 39         <li>
 40           <input type="checkbox"></li>
 41         <li>Pretty row 10</li>
 42         <li>Pretty row 11</li>
 43         <li>Pretty row 12</li>
 44         <li>
 45           <input type="radio"></li>
 46         <li>Pretty row 14</li>
 47         <li>Pretty row 15</li>
 48         <li>Pretty row 16</li>
 49         <li>
 50           <textarea></textarea></li>
 51         <li>Pretty row 18</li>
 52         <li>Pretty row 19</li>
 53         <li>Pretty row 20</li>
 54         <li>
 55           <select>
 56             <option>option</option>
 57           </select></li>
 58       </ul>
 59       <hr />
 60       <ul>
 61         
 62         <li>Pretty row 41</li>
 63         <li>Pretty row 42</li>
 64         <li>Pretty row 43</li>
 65         <li>Pretty row 44</li>
 66         <li>Pretty row 45</li>
 67         <li>Pretty row 46</li>
 68         <li>Pretty row 47</li>
 69         <li>Pretty row 48</li>
 70         <li>Pretty row 49</li>
 71         <li>Pretty row 50</li>
 72       </ul>
 73     </div>
 74   </div>
 75   <footer>
 76     <h1>
 77       Footer</h1>
 78   </footer>
 79   <script src="zepto.js" type="text/javascript"></script>
 80   <script type="text/javascript">
 81     var Scroll = function (opts) {
 82       opts = opts || {};
 83       //检测设备事件支持,确定使用鼠标事件或者touch事件
 84       this._checkEventCompatibility();
 85       this._setBaseParam(opts);
 86       this._addEvent();
 87 
 88       this._initScrollBar();
 89     };
 90 
 91     Scroll.prototype = {
 92       constructor: Scroll,
 93       //检测设备事件兼容
 94       _checkEventCompatibility: function () {
 95         var isTouch = 'ontouchstart' in document.documentElement;
 96         isTouch = true;
 97 
 98         this.start = isTouch ? 'touchstart' : 'mousedown';
 99         this.move = isTouch ? 'touchmove' : 'mousemove';
100         this.end = isTouch ? 'touchend' : 'mouseup';
101         this.startFn;
102         this.moveFn;
103         this.endFn;
104       },
105       //基本参数设置
106       _setBaseParam: function (opts) {
107         this.timeGap = 0; //时间间隔
108         this.touchTime = 0; //开始时间
109         this.isMoveing = false; //是否正在移动
110         this.moveState = 'up'; //移动状态,up right down left
111         this.oTop = 0; //拖动前的top值
112         this.curTop = 0; //当前容器top
113         this.mouseY = 0; //鼠标第一次点下时相对父容器的位置
114         this.animateParam = opts.animateParam || [10, 8, 6, 5, 4, 3, 2, 1, 0, 0, 0]; //动画参数
115         this.cooling = true; //是否处于冷却时间
116         this.steplen = 25; //动画步长
117 
118         this.wrapper = opts.wrapper || $('body');
119         this.dragEl = opts.body;
120         this.wrapper.css({ 'position': 'absolute', 'overflow': 'hidden' });
121         this.dragEl.css('position', 'absolute');
122         this.wrapper.append(this.dragEl);
123       },
124       _initScrollBar: function () {
125         if (!this.dragHeight) {
126           this.dragHeight = this.dragEl.offset().height; //拖动元素高度
127           this.wrapperHeight = this.wrapper.offset().height;
128         }
129         //滚动条缩放比例
130         this.scrollProportion = this.wrapperHeight / this.dragHeight;
131         this.isNeedScrollBar = true;
132         //该种情况无需滚动条
133         if (this.scrollProportion >= 1) {
134           this.isNeedScrollBar = false; ;
135           return false;
136         }
137         //滚动条
138         this.scrollBar = $('<div style="background-color: rgba(0, 0, 0, 0.498039);border: 1px solid rgba(255, 255, 255, 0.901961); width: 5px; border-radius: 3px;  position: absolute; right: 1px; opacity: 0.2;  "></div>');
139         this.wrapper.append(this.scrollBar);
140         this.scrollHeight = parseInt(this.scrollProportion * this.wrapperHeight);
141         this.scrollBar.css('height', this.scrollHeight);
142       },
143       _setScrollTop: function (top, duration) {
144         //滚动条高度
145         if (this.isNeedScrollBar) {
146           top = this._getResetData(top).sTop;
147           top = top < 0 ? (top + 10) : top;
148 
149           var scrollTop = top * (-1);
150           if (typeof duration == 'number') {
151             var _top = parseInt(scrollTop * this.scrollProportion) + 'px';
152             this.scrollBar.animate({
153               '-webkit-transform': 'translate3d(0, ' + _top + ', 0)'
154             }, duration, 'linear');
155 
156           } else {
157             var _st = parseInt(scrollTop * this.scrollProportion)
158             this.scrollBar.css('-webkit-transform', 'translate3d(0, ' + _st + 'px, 0)');
159           }
160           this.scrollBar.css('opacity', '0.8');
161         }
162       },
163       _hideScroll: function () {
164         if (this.isNeedScrollBar) {
165           this.scrollBar.css({ 'opacity': '0.2' });
166         }
167       },
168       _addEvent: function () {
169         var scope = this;
170         this.startFn = function (e) {
171           scope._touchStart.call(scope, e);
172         };
173         this.moveFn = function (e) {
174           scope._touchMove.call(scope, e);
175         };
176         this.endFn = function (e) {
177           scope._touchEnd.call(scope, e);
178         };
179         this.dragEl[0].addEventListener(this.start, this.startFn, false);
180         document.addEventListener(this.move, this.moveFn, false);
181         document.addEventListener(this.end, this.endFn, false);
182       },
183       removeEvent: function () {
184         this.dragEl[0].removeEventListener(this.start, this.startFn);
185         document.removeEventListener(this.move, this.moveFn);
186         document.removeEventListener(this.end, this.endFn);
187       },
188       _touchStart: function (e) {
189         var scope = this;
190         if (this.isMoveing) { e.preventDefault(); return false; }
191         //非运动情况关闭冷却时间
192         this.cooling = false;
193         this.touchTime = e.timeStamp;
194         pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e);
195         //        var top = parseFloat(this.dragEl.css('top')) || 0;
196         var top = this._cssTranslate(this.dragEl);
197         this.mouseY = pos.top - top;
198       },
199       _touchMove: function (e) {
200         if (this.cooling) { e.preventDefault(); return false; }
201 
202         this.isMoveing = true;
203 
204         var pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e);
205 
206         //防止点击时候跳动
207         if (Math.abs((pos.top - this.mouseY) - this.curTop) < 10) { e.preventDefault(); return false; }
208 
209         //先获取相对容器的位置,在将两个鼠标位置相减
210         this.curTop = pos.top - this.mouseY;
211 
212         var resetData = this._getResetData(this.curTop);
213         if (resetData.needReset) {
214           this.curTop = this._resetEdge(this.curTop);
215         }
216 
217         //        this.dragEl.css('top', this.curTop + 'px');
218         this._cssTranslate(this.dragEl, this.curTop);
219 
220         this._setScrollTop(this.curTop);
221         e.preventDefault();
222 
223       },
224       _touchEnd: function (e) {
225         if (this.cooling) { e.preventDefault(); return false; }
226         if (Math.abs(this.oTop - this.curTop) < 10) { e.preventDefault(); return false; }
227         //一次动作结束,开启冷却时间
228         this.cooling = true;
229         var scope = this;
230         this.timeGap = e.timeStamp - this.touchTime;
231         var flag = this.oTop < this.curTop ? 1 : -1; //判断是向上还是向下滚动
232         this.moveState = flag > 0 ? 'up' : 'down';
233 
234         var step = parseInt(this.timeGap / 10 - 10);
235         step = step > 0 ? step : 0;
236         var speed = this.animateParam[step] || 0;
237         var increment = speed * this.steplen * flag;
238         var top = this.curTop;
239         top += increment;
240 
241         var resetData = this._getResetData(top);
242         if (resetData.needReset) {
243           top = this._resetEdge(top);
244           speed = 0;
245         }
246 
247         //!!!此处动画可能导致数据不同步,后期改造需要加入冷却时间
248         if (this.oTop != this.curTop && this.curTop != top) {
249           var duration = 100 + (speed * 20);
250           top += increment;
251           this.dragEl.animate({
252             '-webkit-transform': 'translate3d(0, ' + top + 'px, 0)'
253           }, duration, 'linear', function () {
254             scope.reset.call(scope, top);
255 
256           });
257           this._setScrollTop(top, duration);
258         } else {
259           this.isMoveing = false;
260           this.oTop = top;
261           this.reset(top);
262           this.cooling = false; //关闭冷却时间
263         }
264         this._hideScroll();
265         e.preventDefault();
266       },
267       _resetEdge: function (top) {
268         var h1 = parseInt(this.wrapperHeight / 3);
269         var h2 = parseInt(this.dragHeight * (-1) + this.wrapperHeight * (2 / 3));
270         if (top > 0 && top > h1) top = h1;
271         if (top < 0 && top < h2) top = h2;
272         return top;
273       },
274       _getResetData: function (top) {
275         var needReset = false;
276         var sTop = top;
277         if (top < (-1) * (this.dragHeight - this.wrapperHeight)) { top = (-1) * (this.dragHeight - this.wrapperHeight); needReset = true; }
278         if (top > 0) { top = 0; needReset = true; }
279 
280         return {
281           top: top,
282           sTop: sTop,
283           needReset: needReset
284         };
285       },
286       //超出限制后位置还原
287       reset: function (top) {
288         var scope = this;
289         var needReset = this._getResetData(top).needReset;
290         var top = this._getResetData(top).top;
291 
292         if (needReset) {
293           scope.dragEl.animate({
294              '-webkit-transform': 'translate3d(0, ' + top + 'px, 0)'
295 
296           }, 50, 'linear', function () {
297             scope._reset(top);
298             scope._setScrollTop(top);
299 
300           });
301         } else {
302           scope._reset(top);
303         }
304       },
305       _reset: function (top) {
306         this.oTop = top;
307         this.curTop = top;
308         this.isMoveing = false;
309         this.cooling = false; //关闭冷却时间
310       },
311       //暂时仅用于,操作Y值
312       _cssTranslate: function (el, y) {
313         if (!el) return 0;
314         if (typeof y == 'number') {
315           el.css('-webkit-transform', 'translate3d(0, ' + y + 'px, 0)');
316         }
317         var data = /\((.*)\)/.exec(el.css('-webkit-transform'));
318         if (data && typeof data[1] == 'string') data = data[1].split(',');
319         if (data && typeof data[1] == 'string') return parseInt(data[1]);
320         return 0;
321       },
322       //获取鼠标信息
323       getMousePos: function (event) {
324         var top, left;
325         top = Math.max(document.body.scrollTop, document.documentElement.scrollTop);
326         left = Math.max(document.body.scrollLeft, document.documentElement.scrollLeft);
327         return {
328           top: top + event.clientY,
329           left: left + event.clientX
330         };
331       }
332     };
333     new Scroll({ wrapper: $('#body'), body: $('#wrapper') }); 
334   </script>
335 </body>
336 </html>
View Code

PS:对比下来,我想说,硬件加速的感觉真他妈爽!!!!这段代码没有过多测试,有问题请留言 

停止CSS动画

要停止CSS动画,并且要保存CSS的状态,这个问题其实在三个问题中,我认为是最难的,因为我们可能遇到如下需求:

① 移动过程手指触摸屏幕,动画停止

② 连续滑动时候需要动画加速

如何停止CSS3的动画?

我这里自然处理不到这么复杂的问题,所以就先实现停止动画即可

<div id="wrapper" style="position: absolute; -webkit-transform: translate3d(0px, -558px, 0px);
  -webkit-transition: -webkit-transform 20.1s linear; transition: -webkit-transform 20.1s linear;">
</div>

这个就是zepto一次动画获得的参数,我故意将时间设置的很长,我们要在点击时候马上获取transform,并且重新设置

http://sandbox.runjs.cn/show/vgekfj8f

 

  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   <meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
  6   <style type="text/css">
  7     body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, textarea, p, blockquote, th, td, hr, button, article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { margin: 0; padding: 0; }
  8     body { font: normal 14px/1.5 "Arial" , "Lucida Grande" ,Verdana, "Microsoft YaHei" , "hei"; -webkit-font-smoothing: antialiased; color: #000; background: #ccc; }
  9     header { position: absolute; top: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
 10     footer { position: absolute; bottom: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
 11     h1 { display: block; font-size: 2em; font-weight: bold; font-weight: 500; text-align: center; color: White; }
 12     
 13     #body { position: absolute; top: 50px; bottom: 50px; background: #fff; width: 100%; overflow: hidden; }
 14     #wrapper { width: 100%; }
 15     
 16     
 17     #wrapper li { padding: 0 10px; height: 40px; line-height: 40px; border-bottom: 1px solid #ccc; border-top: 1px solid #fff; background-color: #fafafa; font-size: 14px; }
 18   </style>
 19     <script id="others_zepto_10rc1" type="text/javascript" class="library" src="/js/sandbox/other/zepto.min.js"></script>
 20 </head>
 21 <body>
 22   <header id="header">
 23     <h1>
 24       Header</h1>
 25   </header>
 26   <div id="body">
 27     <div id="wrapper">
 28       body
 29       <ul>
 30         <li>Pretty row 1</li>
 31         <li>Pretty row 2</li>
 32         <li>Pretty row 3</li>
 33         <li>Pretty row 4</li>
 34         <li>
 35           <input type="text"></li>
 36         <li>Pretty row 6</li>
 37         <li>Pretty row 7</li>
 38         <li>Pretty row 8</li>
 39         <li>
 40           <input type="checkbox"></li>
 41         <li>Pretty row 10</li>
 42         <li>Pretty row 11</li>
 43         <li>Pretty row 12</li>
 44         <li>
 45           <input type="radio"></li>
 46         <li>Pretty row 14</li>
 47         <li>Pretty row 15</li>
 48         <li>Pretty row 16</li>
 49         <li>
 50           <textarea></textarea></li>
 51         <li>Pretty row 18</li>
 52         <li>Pretty row 19</li>
 53         <li>Pretty row 20</li>
 54         <li>
 55           <select>
 56             <option>option</option>
 57           </select></li>
 58       </ul>
 59       <hr />
 60       <ul>
 61         
 62         <li>Pretty row 41</li>
 63         <li>Pretty row 42</li>
 64         <li>Pretty row 43</li>
 65         <li>Pretty row 44</li>
 66         <li>Pretty row 45</li>
 67         <li>Pretty row 46</li>
 68         <li>Pretty row 47</li>
 69         <li>Pretty row 48</li>
 70         <li>Pretty row 49</li>
 71         <li>Pretty row 50</li>
 72       </ul>
 73     </div>
 74   </div>
 75   <footer>
 76     <h1>
 77       Footer</h1>
 78   </footer>
 79   <script src="zepto.js" type="text/javascript"></script>
 80   <script type="text/javascript">
 81     var Scroll = function (opts) {
 82       opts = opts || {};
 83       //检测设备事件支持,确定使用鼠标事件或者touch事件
 84       this._checkEventCompatibility();
 85       this._setBaseParam(opts);
 86       this._addEvent();
 87 
 88       this._initScrollBar();
 89     };
 90 
 91     Scroll.prototype = {
 92       constructor: Scroll,
 93       //检测设备事件兼容
 94       _checkEventCompatibility: function () {
 95         var isTouch = 'ontouchstart' in document.documentElement;
 96         //        isTouch = true;
 97 
 98         this.start = isTouch ? 'touchstart' : 'mousedown';
 99         this.move = isTouch ? 'touchmove' : 'mousemove';
100         this.end = isTouch ? 'touchend' : 'mouseup';
101         this.startFn;
102         this.moveFn;
103         this.endFn;
104       },
105       //基本参数设置
106       _setBaseParam: function (opts) {
107         this.timeGap = 0; //时间间隔
108         this.touchTime = 0; //开始时间
109         this.isMoveing = false; //是否正在移动
110         this.moveState = 'up'; //移动状态,up right down left
111         this.oTop = 0; //拖动前的top值
112         this.curTop = 0; //当前容器top
113         this.mouseY = 0; //鼠标第一次点下时相对父容器的位置
114         this.animateParam = opts.animateParam || [10, 8, 6, 5, 4, 3, 2, 1, 0, 0, 0]; //动画参数
115         this.cooling = true; //是否处于冷却时间
116         this.steplen = 25; //动画步长
117 
118         this.wrapper = opts.wrapper || $('body');
119         this.dragEl = opts.body;
120         this.wrapper.css({ 'position': 'absolute', 'overflow': 'hidden' });
121         this.dragEl.css('position', 'absolute');
122         this.wrapper.append(this.dragEl);
123       },
124       _initScrollBar: function () {
125         if (!this.dragHeight) {
126           this.dragHeight = this.dragEl.offset().height; //拖动元素高度
127           this.wrapperHeight = this.wrapper.offset().height;
128         }
129         //滚动条缩放比例
130         this.scrollProportion = this.wrapperHeight / this.dragHeight;
131         this.isNeedScrollBar = true;
132         //该种情况无需滚动条
133         if (this.scrollProportion >= 1) {
134           this.isNeedScrollBar = false; ;
135           return false;
136         }
137         //滚动条
138         this.scrollBar = $('<div style="background-color: rgba(0, 0, 0, 0.498039);border: 1px solid rgba(255, 255, 255, 0.901961); width: 5px; border-radius: 3px;  position: absolute; right: 1px; opacity: 0.2;  "></div>');
139         this.wrapper.append(this.scrollBar);
140         this.scrollHeight = parseInt(this.scrollProportion * this.wrapperHeight);
141         this.scrollBar.css('height', this.scrollHeight);
142       },
143       _setScrollTop: function (top, duration) {
144         //滚动条高度
145         if (this.isNeedScrollBar) {
146           top = this._getResetData(top).sTop;
147           top = top < 0 ? (top + 10) : top;
148 
149           var scrollTop = top * (-1);
150           if (typeof duration == 'number') {
151             var _top = parseInt(scrollTop * this.scrollProportion) + 'px';
152             this.scrollBar.animate({
153               '-webkit-transform': 'translate3d(0, ' + _top + ', 0)'
154             }, duration, 'linear');
155 
156           } else {
157             var _st = parseInt(scrollTop * this.scrollProportion)
158             this.scrollBar.css('-webkit-transform', 'translate3d(0, ' + _st + 'px, 0)');
159           }
160           this.scrollBar.css('opacity', '0.8');
161         }
162       },
163       _hideScroll: function () {
164         if (this.isNeedScrollBar) {
165           this.scrollBar.css({ 'opacity': '0.2' });
166         }
167       },
168       _addEvent: function () {
169         var scope = this;
170         this.startFn = function (e) {
171           scope._touchStart.call(scope, e);
172         };
173         this.moveFn = function (e) {
174           scope._touchMove.call(scope, e);
175         };
176         this.endFn = function (e) {
177           scope._touchEnd.call(scope, e);
178         };
179         this.dragEl[0].addEventListener(this.start, this.startFn, false);
180         document.addEventListener(this.move, this.moveFn, false);
181         document.addEventListener(this.end, this.endFn, false);
182       },
183       removeEvent: function () {
184         this.dragEl[0].removeEventListener(this.start, this.startFn);
185         document.removeEventListener(this.move, this.moveFn);
186         document.removeEventListener(this.end, this.endFn);
187       },
188       _touchStart: function (e) {
189         var scope = this;
190         window.dragEl = this.dragEl;
191 
192         if (this.isMoveing) {
193 
194 
195 
196           var el = this.dragEl[0];
197           var computedStyle = document.defaultView.getComputedStyle(el, null);
198           //          computedStyle.getPropertyValue("width");
199 
200           var top = 0;
201           var data = /\((.*)\)/.exec(computedStyle.getPropertyValue("-webkit-transform"));
202           if (typeof data == 'object') data = data[1].split(',');
203           if (typeof data == 'object') top = parseInt(data[5]);
204           console.log(top);
205 
206 
207                     this.dragEl.css('-webkit-transition', '-webkit-transform 0s linear');
208                     this.dragEl.css('transition', '-webkit-transform 0s linear');
209 
210                     this._cssTranslate(this.dragEl, top);
211                     this._setScrollTop(top);
212 
213 
214                     this.isMoveing = false;
215           e.preventDefault(); return false;
216         }
217 
218         this.clickEl = e.target;
219 
220 
221 
222 
223         //非运动情况关闭冷却时间
224         this.cooling = false;
225         this.touchTime = e.timeStamp;
226         pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e);
227         //        var top = parseFloat(this.dragEl.css('top')) || 0;
228         var top = this._cssTranslate(this.dragEl);
229         this.mouseY = pos.top - top;
230       },
231       _touchMove: function (e) {
232         if (this.cooling) { e.preventDefault(); return false; }
233 
234         this.isMoveing = true;
235 
236         var pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e);
237 
238         //防止点击时候跳动
239         if (Math.abs((pos.top - this.mouseY) - this.curTop) < 10) { e.preventDefault(); return false; }
240 
241         //先获取相对容器的位置,在将两个鼠标位置相减
242         this.curTop = pos.top - this.mouseY;
243 
244         var resetData = this._getResetData(this.curTop);
245         if (resetData.needReset) {
246           this.curTop = this._resetEdge(this.curTop);
247         }
248 
249         //        this.dragEl.css('top', this.curTop + 'px');
250         this._cssTranslate(this.dragEl, this.curTop);
251 
252         this._setScrollTop(this.curTop);
253         e.preventDefault();
254 
255       },
256       _touchEnd: function (e) {
257 
258         if (this._needFocus(this.clickEl)) {
259           $(this.clickEl).focus();
260           return;
261         }
262 
263 
264         if (this.cooling) { e.preventDefault(); return false; }
265         if (Math.abs(this.oTop - this.curTop) < 10) { e.preventDefault(); return false; }
266         //一次动作结束,开启冷却时间
267         this.cooling = true;
268         var scope = this;
269         this.timeGap = e.timeStamp - this.touchTime;
270         var flag = this.oTop < this.curTop ? 1 : -1; //判断是向上还是向下滚动
271         this.moveState = flag > 0 ? 'up' : 'down';
272 
273         var step = parseInt(this.timeGap / 10 - 10);
274         step = step > 0 ? step : 0;
275         var speed = this.animateParam[step] || 0;
276         var increment = speed * this.steplen * flag;
277         var top = this.curTop;
278         top += increment;
279 
280         var resetData = this._getResetData(top);
281         if (resetData.needReset) {
282           top = this._resetEdge(top);
283           speed = 0;
284         }
285 
286         speed = 1000;
287 
288         //!!!此处动画可能导致数据不同步,后期改造需要加入冷却时间
289         if (this.oTop != this.curTop && this.curTop != top) {
290           var duration = 100 + (speed * 20);
291           top += increment;
292           this.dragEl.animate({
293             '-webkit-transform': 'translate3d(0, ' + top + 'px, 0)'
294           }, duration, 'linear', function () {
295             scope.reset.call(scope, top);
296 
297           });
298           this._setScrollTop(top, duration);
299         } else {
300           this.isMoveing = false;
301           this.oTop = top;
302           this.reset(top);
303           this.cooling = false; //关闭冷却时间
304         }
305         this._hideScroll();
306         e.preventDefault();
307       },
308       _resetEdge: function (top) {
309         var h1 = parseInt(this.wrapperHeight / 3);
310         var h2 = parseInt(this.dragHeight * (-1) + this.wrapperHeight * (2 / 3));
311         if (top > 0 && top > h1) top = h1;
312         if (top < 0 && top < h2) top = h2;
313         return top;
314       },
315       _getResetData: function (top) {
316         var needReset = false;
317         var sTop = top;
318         if (top < (-1) * (this.dragHeight - this.wrapperHeight)) { top = (-1) * (this.dragHeight - this.wrapperHeight); needReset = true; }
319         if (top > 0) { top = 0; needReset = true; }
320 
321         return {
322           top: top,
323           sTop: sTop,
324           needReset: needReset
325         };
326       },
327       //超出限制后位置还原
328       reset: function (top) {
329         var scope = this;
330         var needReset = this._getResetData(top).needReset;
331         var top = this._getResetData(top).top;
332 
333         if (needReset) {
334           scope.dragEl.animate({
335             '-webkit-transform': 'translate3d(0, ' + top + 'px, 0)'
336 
337           }, 50, 'linear', function () {
338             scope._reset(top);
339             scope._setScrollTop(top);
340 
341           });
342         } else {
343           scope._reset(top);
344         }
345       },
346       _reset: function (top) {
347         this.oTop = top;
348         this.curTop = top;
349         this.isMoveing = false;
350         this.cooling = false; //关闭冷却时间
351       },
352       //暂时仅用于,操作Y值
353       _cssTranslate: function (el, y) {
354         if (!el) return 0;
355         if (typeof y == 'number') {
356           el.css('-webkit-transform', 'translate3d(0, ' + y + 'px, 0)');
357         }
358         var data = /\((.*)\)/.exec(el.css('-webkit-transform'));
359         if (data && typeof data[1] == 'string') data = data[1].split(',');
360         if (data && typeof data[1] == 'string') return parseInt(data[1]);
361         return 0;
362       },
363       _needFocus: function (el) {
364         switch (el.nodeName.toLowerCase()) {
365           case 'textarea':
366           case 'select':
367             return true;
368           case 'input':
369             switch (el.type) {
370               case 'button':
371               case 'checkbox':
372               case 'file':
373               case 'image':
374               case 'radio':
375               case 'submit':
376                 return false;
377             }
378             return !el.disabled && !el.readOnly;
379           default:
380             return (/\bneedfocus\b/).test(el.className);
381         }
382       },
383       //获取鼠标信息
384       getMousePos: function (event) {
385         var top, left;
386         top = Math.max(document.body.scrollTop, document.documentElement.scrollTop);
387         left = Math.max(document.body.scrollLeft, document.documentElement.scrollLeft);
388         return {
389           top: top + event.clientY,
390           left: left + event.clientX
391         };
392       }
393     };
394     new Scroll({ wrapper: $('#body'), body: $('#wrapper') }); 
395   </script>
396 </body>
397 </html>
View Code

PS:时间比较晚了,代码未做检测,各位包含 

文本获取焦点

由于e.preventDefault的效果,所以我们里面的按钮点击一键文本框获取焦点有点不好使,我这里主要解决文本获取焦点即可

我们在touchstart时候可以获取e.target,在touchend时候若是判断是一次点击事件并且target为文本框的话,便获取焦点。

PS:我这里因为暂时不用作生产,先简单实现即可

http://sandbox.runjs.cn/show/djkrwwno

  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   <meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
  6   <style type="text/css">
  7     body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, textarea, p, blockquote, th, td, hr, button, article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { margin: 0; padding: 0; }
  8     body { font: normal 14px/1.5 "Arial" , "Lucida Grande" ,Verdana, "Microsoft YaHei" , "hei"; -webkit-font-smoothing: antialiased; color: #000; background: #ccc; }
  9     header { position: absolute; top: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
 10     footer { position: absolute; bottom: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
 11     h1 { display: block; font-size: 2em; font-weight: bold; font-weight: 500; text-align: center; color: White; }
 12     
 13     #body { position: absolute; top: 50px; bottom: 50px; background: #fff; width: 100%; overflow: hidden; }
 14     #wrapper { width: 100%; }
 15     
 16     
 17     #wrapper li { padding: 0 10px; height: 40px; line-height: 40px; border-bottom: 1px solid #ccc; border-top: 1px solid #fff; background-color: #fafafa; font-size: 14px; }
 18   </style>
 19     <script id="others_zepto_10rc1" type="text/javascript" class="library" src="/js/sandbox/other/zepto.min.js"></script>
 20 </head>
 21 <body>
 22   <header id="header">
 23     <h1>
 24       Header</h1>
 25   </header>
 26   <div id="body">
 27     <div id="wrapper">
 28       body
 29       <ul>
 30         <li>Pretty row 1</li>
 31         <li>Pretty row 2</li>
 32         <li>Pretty row 3</li>
 33         <li>Pretty row 4</li>
 34         <li>
 35           <input type="text"></li>
 36         <li>Pretty row 6</li>
 37         <li>Pretty row 7</li>
 38         <li>Pretty row 8</li>
 39         <li>
 40           <input type="checkbox"></li>
 41         <li>Pretty row 10</li>
 42         <li>Pretty row 11</li>
 43         <li>Pretty row 12</li>
 44         <li>
 45           <input type="radio"></li>
 46         <li>Pretty row 14</li>
 47         <li>Pretty row 15</li>
 48         <li>Pretty row 16</li>
 49         <li>
 50           <textarea></textarea></li>
 51         <li>Pretty row 18</li>
 52         <li>Pretty row 19</li>
 53         <li>Pretty row 20</li>
 54         <li>
 55           <select>
 56             <option>option</option>
 57           </select></li>
 58       </ul>
 59       <hr />
 60       <ul>
 61         
 62         <li>Pretty row 41</li>
 63         <li>Pretty row 42</li>
 64         <li>Pretty row 43</li>
 65         <li>Pretty row 44</li>
 66         <li>Pretty row 45</li>
 67         <li>Pretty row 46</li>
 68         <li>Pretty row 47</li>
 69         <li>Pretty row 48</li>
 70         <li>Pretty row 49</li>
 71         <li>Pretty row 50</li>
 72       </ul>
 73     </div>
 74   </div>
 75   <footer>
 76     <h1>
 77       Footer</h1>
 78   </footer>
 79   <script src="zepto.js" type="text/javascript"></script>
 80   <script type="text/javascript">
 81     var Scroll = function (opts) {
 82       opts = opts || {};
 83       //检测设备事件支持,确定使用鼠标事件或者touch事件
 84       this._checkEventCompatibility();
 85       this._setBaseParam(opts);
 86       this._addEvent();
 87 
 88       this._initScrollBar();
 89     };
 90 
 91     Scroll.prototype = {
 92       constructor: Scroll,
 93       //检测设备事件兼容
 94       _checkEventCompatibility: function () {
 95         var isTouch = 'ontouchstart' in document.documentElement;
 96 
 97         this.start = isTouch ? 'touchstart' : 'mousedown';
 98         this.move = isTouch ? 'touchmove' : 'mousemove';
 99         this.end = isTouch ? 'touchend' : 'mouseup';
100         this.startFn;
101         this.moveFn;
102         this.endFn;
103       },
104       //基本参数设置
105       _setBaseParam: function (opts) {
106         this.timeGap = 0; //时间间隔
107         this.touchTime = 0; //开始时间
108         this.isMoveing = false; //是否正在移动
109         this.moveState = 'up'; //移动状态,up right down left
110         this.oTop = 0; //拖动前的top值
111         this.curTop = 0; //当前容器top
112         this.mouseY = 0; //鼠标第一次点下时相对父容器的位置
113         this.animateParam = opts.animateParam || [10, 8, 6, 5, 4, 3, 2, 1, 0, 0, 0]; //动画参数
114         this.cooling = true; //是否处于冷却时间
115         this.steplen = 25; //动画步长
116 
117         this.wrapper = opts.wrapper || $('body');
118         this.dragEl = opts.body;
119         this.wrapper.css({ 'position': 'absolute', 'overflow': 'hidden' });
120         this.dragEl.css('position', 'absolute');
121         this.wrapper.append(this.dragEl);
122       },
123       _initScrollBar: function () {
124         if (!this.dragHeight) {
125           this.dragHeight = this.dragEl.offset().height; //拖动元素高度
126           this.wrapperHeight = this.wrapper.offset().height;
127         }
128         //滚动条缩放比例
129         this.scrollProportion = this.wrapperHeight / this.dragHeight;
130         this.isNeedScrollBar = true;
131         //该种情况无需滚动条
132         if (this.scrollProportion >= 1) {
133           this.isNeedScrollBar = false; ;
134           return false;
135         }
136         //滚动条
137         this.scrollBar = $('<div style="background-color: rgba(0, 0, 0, 0.498039);border: 1px solid rgba(255, 255, 255, 0.901961); width: 5px; border-radius: 3px;  position: absolute; right: 1px; opacity: 0.2;  "></div>');
138         this.wrapper.append(this.scrollBar);
139         this.scrollHeight = parseInt(this.scrollProportion * this.wrapperHeight);
140         this.scrollBar.css('height', this.scrollHeight);
141       },
142       _setScrollTop: function (top, duration) {
143         //滚动条高度
144         if (this.isNeedScrollBar) {
145           top = this._getResetData(top).sTop;
146           top = top < 0 ? (top + 10) : top;
147 
148           var scrollTop = top * (-1);
149           if (typeof duration == 'number') {
150             var _top = parseInt(scrollTop * this.scrollProportion) + 'px';
151             this.scrollBar.animate({
152               '-webkit-transform': 'translate3d(0, ' + _top + ', 0)'
153             }, duration, 'linear');
154 
155           } else {
156             var _st = parseInt(scrollTop * this.scrollProportion)
157             this.scrollBar.css('-webkit-transform', 'translate3d(0, ' + _st + 'px, 0)');
158           }
159           this.scrollBar.css('opacity', '0.8');
160         }
161       },
162       _hideScroll: function () {
163         if (this.isNeedScrollBar) {
164           this.scrollBar.css({ 'opacity': '0.2' });
165         }
166       },
167       _addEvent: function () {
168         var scope = this;
169         this.startFn = function (e) {
170           scope._touchStart.call(scope, e);
171         };
172         this.moveFn = function (e) {
173           scope._touchMove.call(scope, e);
174         };
175         this.endFn = function (e) {
176           scope._touchEnd.call(scope, e);
177         };
178         this.dragEl[0].addEventListener(this.start, this.startFn, false);
179         document.addEventListener(this.move, this.moveFn, false);
180         document.addEventListener(this.end, this.endFn, false);
181       },
182       removeEvent: function () {
183         this.dragEl[0].removeEventListener(this.start, this.startFn);
184         document.removeEventListener(this.move, this.moveFn);
185         document.removeEventListener(this.end, this.endFn);
186       },
187       _touchStart: function (e) {
188         var scope = this;
189         if (this.isMoveing) { e.preventDefault(); return false; }
190 
191         this.clickEl = e.target;
192 
193         //非运动情况关闭冷却时间
194         this.cooling = false;
195         this.touchTime = e.timeStamp;
196         pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e);
197         //        var top = parseFloat(this.dragEl.css('top')) || 0;
198         var top = this._cssTranslate(this.dragEl);
199         this.mouseY = pos.top - top;
200       },
201       _touchMove: function (e) {
202         if (this.cooling) { e.preventDefault(); return false; }
203 
204         this.isMoveing = true;
205 
206         var pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e);
207 
208         //防止点击时候跳动
209         if (Math.abs((pos.top - this.mouseY) - this.curTop) < 10) { e.preventDefault(); return false; }
210 
211         //先获取相对容器的位置,在将两个鼠标位置相减
212         this.curTop = pos.top - this.mouseY;
213 
214         var resetData = this._getResetData(this.curTop);
215         if (resetData.needReset) {
216           this.curTop = this._resetEdge(this.curTop);
217         }
218 
219         //        this.dragEl.css('top', this.curTop + 'px');
220         this._cssTranslate(this.dragEl, this.curTop);
221 
222         this._setScrollTop(this.curTop);
223         e.preventDefault();
224 
225       },
226       _touchEnd: function (e) {
227 
228         if (this._needFocus(this.clickEl)) {
229           $(this.clickEl).focus();
230           return;
231         }
232 
233 
234         if (this.cooling) { e.preventDefault(); return false; }
235         if (Math.abs(this.oTop - this.curTop) < 10) { e.preventDefault(); return false; }
236         //一次动作结束,开启冷却时间
237         this.cooling = true;
238         var scope = this;
239         this.timeGap = e.timeStamp - this.touchTime;
240         var flag = this.oTop < this.curTop ? 1 : -1; //判断是向上还是向下滚动
241         this.moveState = flag > 0 ? 'up' : 'down';
242 
243         var step = parseInt(this.timeGap / 10 - 10);
244         step = step > 0 ? step : 0;
245         var speed = this.animateParam[step] || 0;
246         var increment = speed * this.steplen * flag;
247         var top = this.curTop;
248         top += increment;
249 
250         var resetData = this._getResetData(top);
251         if (resetData.needReset) {
252           top = this._resetEdge(top);
253           speed = 0;
254         }
255 
256         //!!!此处动画可能导致数据不同步,后期改造需要加入冷却时间
257         if (this.oTop != this.curTop && this.curTop != top) {
258           var duration = 100 + (speed * 20);
259           top += increment;
260           this.dragEl.animate({
261             '-webkit-transform': 'translate3d(0, ' + top + 'px, 0)'
262           }, duration, 'linear', function () {
263             scope.reset.call(scope, top);
264 
265           });
266           this._setScrollTop(top, duration);
267         } else {
268           this.isMoveing = false;
269           this.oTop = top;
270           this.reset(top);
271           this.cooling = false; //关闭冷却时间
272         }
273         this._hideScroll();
274         e.preventDefault();
275       },
276       _resetEdge: function (top) {
277         var h1 = parseInt(this.wrapperHeight / 3);
278         var h2 = parseInt(this.dragHeight * (-1) + this.wrapperHeight * (2 / 3));
279         if (top > 0 && top > h1) top = h1;
280         if (top < 0 && top < h2) top = h2;
281         return top;
282       },
283       _getResetData: function (top) {
284         var needReset = false;
285         var sTop = top;
286         if (top < (-1) * (this.dragHeight - this.wrapperHeight)) { top = (-1) * (this.dragHeight - this.wrapperHeight); needReset = true; }
287         if (top > 0) { top = 0; needReset = true; }
288 
289         return {
290           top: top,
291           sTop: sTop,
292           needReset: needReset
293         };
294       },
295       //超出限制后位置还原
296       reset: function (top) {
297         var scope = this;
298         var needReset = this._getResetData(top).needReset;
299         var top = this._getResetData(top).top;
300 
301         if (needReset) {
302           scope.dragEl.animate({
303             '-webkit-transform': 'translate3d(0, ' + top + 'px, 0)'
304 
305           }, 50, 'linear', function () {
306             scope._reset(top);
307             scope._setScrollTop(top);
308 
309           });
310         } else {
311           scope._reset(top);
312         }
313       },
314       _reset: function (top) {
315         this.oTop = top;
316         this.curTop = top;
317         this.isMoveing = false;
318         this.cooling = false; //关闭冷却时间
319       },
320       //暂时仅用于,操作Y值
321       _cssTranslate: function (el, y) {
322         if (!el) return 0;
323         if (typeof y == 'number') {
324           el.css('-webkit-transform', 'translate3d(0, ' + y + 'px, 0)');
325         }
326         var data = /\((.*)\)/.exec(el.css('-webkit-transform'));
327         if (data && typeof data[1] == 'string') data = data[1].split(',');
328         if (data && typeof data[1] == 'string') return parseInt(data[1]);
329         return 0;
330       },
331       _needFocus: function (el) {
332         switch (el.nodeName.toLowerCase()) {
333           case 'textarea':
334           case 'select':
335             return true;
336           case 'input':
337             switch (el.type) {
338               case 'button':
339               case 'checkbox':
340               case 'file':
341               case 'image':
342               case 'radio':
343               case 'submit':
344                 return false;
345             }
346             return !el.disabled && !el.readOnly;
347           default:
348             return (/\bneedfocus\b/).test(el.className);
349         }
350       },
351       //获取鼠标信息
352       getMousePos: function (event) {
353         var top, left;
354         top = Math.max(document.body.scrollTop, document.documentElement.scrollTop);
355         left = Math.max(document.body.scrollLeft, document.documentElement.scrollLeft);
356         return {
357           top: top + event.clientY,
358           left: left + event.clientX
359         };
360       }
361     };
362     new Scroll({ wrapper: $('#body'), body: $('#wrapper') }); 
363   </script>
364 </body>
365 </html>
View Code

如此,文本类标签便可以获得焦点了,其它的东西,各位通过代码自己搞下吧,现在只剩下停止动画了......有点累

核心代码:

 1 if (this._needFocus(this.clickEl)) {
 2   $(this.clickEl).focus();
 3   return;
 4 }
 5 _needFocus: function (el) {
 6   switch (el.nodeName.toLowerCase()) {
 7     case 'textarea':
 8     case 'select':
 9       return true;
10     case 'input':
11       switch (el.type) {
12         case 'button':
13         case 'checkbox':
14         case 'file':
15         case 'image':
16         case 'radio':
17         case 'submit':
18           return false;
19       }
20       return !el.disabled && !el.readOnly;
21     default:
22       return (/\bneedfocus\b/).test(el.className);
23   }
24 },

结语

今天,我们一起实现了简单的iScroll的功能,明天我们一起来进行源码学习,看看iScroll到底有何优点

如果您发现文中有何问题,请与我联系。

posted on 2013-12-28 17:13  叶小钗  阅读(6639)  评论(13编辑  收藏