【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>
<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>
其实这个属性也是可以实现三行布局的基本功能的,但是有以下几个问题:
① 没有滚动条
② 滚动不顺畅
③ 手机浏览器支持良莠不齐
但是这些缺点也不能掩盖他最大的优点:原生性!!!原生的就是最好的,如果哪天这个属性升级的话,前途就好了
页面切换动画
要伪装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>
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>
这个是我们第一步形成的代码,他具有以下问题待解决:
① 由于是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>
PS:对比下来,我想说,硬件加速的感觉真他妈爽!!!!这段代码没有过多测试,有问题请留言
停止CSS动画
要停止CSS动画,并且要保存CSS的状态,这个问题其实在三个问题中,我认为是最难的,因为我们可能遇到如下需求:
① 移动过程手指触摸屏幕,动画停止
② 连续滑动时候需要动画加速
我这里自然处理不到这么复杂的问题,所以就先实现停止动画即可
<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>
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>
如此,文本类标签便可以获得焦点了,其它的东西,各位通过代码自己搞下吧,现在只剩下停止动画了......有点累
核心代码:
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到底有何优点
如果您发现文中有何问题,请与我联系。