首页 技术 正文
技术 2022年11月17日
0 收藏 977 点赞 2,638 浏览 47681 个字

前言

相信对移动端有了解的朋友对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

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

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

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

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

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

【iScroll源码学习00】模拟iScroll

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

【iScroll源码学习00】模拟iScroll

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

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

【iScroll源码学习00】模拟iScroll

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这个属性,这里提供一段代码,两个截图:

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta name="viewport" content="width=device-width">
</head>
<body>
方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨
</body>
</html>
<meta name="viewport" content="width=100">
<!-- 这里以此给width赋值:① 默认情况/② device-width/③ 100 ->

【iScroll源码学习00】模拟iScroll

为何出现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:

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style type="text/css">
#header { width: 100%; height: 50px; position: absolute; top: 0; left: 0; }
#footer { width: 100%; height: 50px; position: absolute; bottom: 0; left: 0; }
#body { height: 180px; margin: 60px 0; overflow: auto; }
div { border: 1px solid black; }
</style>
</head>
<body>
<div id="header">header</div>
<div id="body">方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方
<input type="text" />
法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨 方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方
<input type="text" />
法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨方法是多少的发生的范德萨 </div>
<div id="footer">footer</div> </body>
</html>

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

① 没有滚动条

② 滚动不顺畅

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

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

页面切换动画

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

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

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

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

实现iScroll功能

基本dom结构

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

 <html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style type="text/css">
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; }
body { font: normal 14px/1.5 "Arial" , "Lucida Grande" ,Verdana, "Microsoft YaHei" , "hei"; -webkit-font-smoothing: antialiased; color: #000; background: #f5f5f5; }
header { position: absolute; top: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
footer { position: absolute; bottom: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
h1 { display: block; font-size: 2em; font-weight: bold; font-weight: 500; text-align: center; color: White; }
#body { background: #fff; border: 1px solid #cfcfcf; width: 96%; height: 100%; margin: 50px auto; padding: 4px; }
</style>
</head>
<body>
<header id="header">
<h1>
Header</h1>
</header>
<div id="body">
body
</div>
<footer>
<h1>
Footer</h1>
</footer>
</body>
</html>

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

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style type="text/css">
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; }
body { font: normal 14px/1.5 "Arial" , "Lucida Grande" ,Verdana, "Microsoft YaHei" , "hei"; -webkit-font-smoothing: antialiased; color: #000; background: #ccc; }
header { position: absolute; top: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
footer { position: absolute; bottom: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
h1 { display: block; font-size: 2em; font-weight: bold; font-weight: 500; text-align: center; color: White; } #body { position: absolute; top: 50px; bottom: 50px; background: #fff; width: 100%; overflow: hidden; }
#wrapper { width: 100%; } #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; }
</style>
</head>
<body>
<header id="header">
<h1>
Header</h1>
</header>
<div id="body">
<div id="wrapper">
body
<ul>
<li>Pretty row 1</li>
<li>Pretty row 2</li>
<li>Pretty row 3</li>
<li>Pretty row 4</li>
<li>
<input type="text"></li>
<li>Pretty row 6</li>
<li>Pretty row 7</li>
<li>Pretty row 8</li>
<li>
<input type="checkbox"></li>
<li>Pretty row 10</li>
<li>Pretty row 11</li>
<li>Pretty row 12</li>
<li>
<input type="radio"></li>
<li>Pretty row 14</li>
<li>Pretty row 15</li>
<li>Pretty row 16</li>
<li>
<textarea></textarea></li>
<li>Pretty row 18</li>
<li>Pretty row 19</li>
<li>Pretty row 20</li>
<li>
<select>
<option>option</option>
</select></li>
<li>Pretty row 22</li>
<li>Pretty row 23</li>
<li>Pretty row 24</li>
</ul>
<hr />
<ul>
<li>Pretty row 25</li>
<li>Pretty row 26</li>
<li>Pretty row 27</li>
<li>Pretty row 28</li>
<li>Pretty row 29</li>
<li>Pretty row 30</li>
<li>Pretty row 31</li>
<li>Pretty row 32</li>
<li>Pretty row 33</li>
<li>Pretty row 34</li>
<li>Pretty row 35</li>
<li>Pretty row 36</li>
<li>Pretty row 37</li>
<li>Pretty row 38</li>
<li>Pretty row 39</li>
<li>Pretty row 40</li>
<li>Pretty row 41</li>
<li>Pretty row 42</li>
<li>Pretty row 43</li>
<li>Pretty row 44</li>
<li>Pretty row 45</li>
<li>Pretty row 46</li>
<li>Pretty row 47</li>
<li>Pretty row 48</li>
<li>Pretty row 49</li>
<li>Pretty row 50</li>
</ul>
</div>
</div>
<footer>
<h1>
Footer</h1>
</footer>
<script src="../../zepto/zepto-1.0/src/zepto.js" type="text/javascript"></script>
<script type="text/javascript">
var Scroll = function (opts) {
opts = opts || {};
//检测设备事件支持,确定使用鼠标事件或者touch事件
this._checkEventCompatibility();
this._setBaseParam(opts);
this._initScrollBar();
// this._addEvent();
}; Scroll.prototype = {
constructor: Scroll,
//检测设备事件兼容
_checkEventCompatibility: function () {
var isTouch = 'ontouchstart' in document.documentElement;
this.start = isTouch ? 'touchstart' : 'mousedown';
this.move = isTouch ? 'touchmove' : 'mousemove';
this.end = isTouch ? 'touchend' : 'mouseup';
this.startFn;
this.moveFn;
this.endFn;
},
//基本参数设置
_setBaseParam: function (opts) {
this.timeGap = 0; //时间间隔
this.touchTime = 0; //开始时间
this.isMoveing = false; //是否正在移动
this.moveState = 'up'; //移动状态,up right down left
this.oTop = 0; //拖动前的top值
this.curTop = 0; //当前容器top
this.mouseY = 0; //鼠标第一次点下时相对父容器的位置
this.animateParam = opts.animateParam || [10, 8, 6, 5, 4, 3, 2, 1, 0, 0, 0]; //动画参数
this.cooling = true; //是否处于冷却时间
this.steplen = 25; //动画步长 this.wrapper = opts.wrapper || $('body');
this.dragEl = opts.body;
this.wrapper.css({ 'position': 'absolute', 'overflow': 'hidden' });
this.dragEl.css('position', 'absolute');
this.wrapper.append(this.dragEl);
},
_initScrollBar: function () {
if (!this.dragHeight) {
this.dragHeight = this.dragEl.offset().height; //拖动元素高度
this.wrapperHeight = this.wrapper.offset().height;
}
//滚动条缩放比例
this.scrollProportion = this.wrapperHeight / this.dragHeight;
this.isNeedScrollBar = true;
//该种情况无需滚动条
if (this.scrollProportion >= 1) {
this.isNeedScrollBar = false; ;
return false;
}
//滚动条
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>');
this.wrapper.append(this.scrollBar);
this.scrollHeight = parseInt(this.scrollProportion * this.wrapperHeight);
this.scrollBar.css('height', this.scrollHeight);
} };
new Scroll({ wrapper: $('#body'), body: $('#wrapper') });
</script>
</body>
</html>

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

事件绑定

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

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style type="text/css">
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; }
body { font: normal 14px/1.5 "Arial" , "Lucida Grande" ,Verdana, "Microsoft YaHei" , "hei"; -webkit-font-smoothing: antialiased; color: #000; background: #ccc; }
header { position: absolute; top: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
footer { position: absolute; bottom: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
h1 { display: block; font-size: 2em; font-weight: bold; font-weight: 500; text-align: center; color: White; } #body { position: absolute; top: 50px; bottom: 50px; background: #fff; width: 100%; overflow: hidden; }
#wrapper { width: 100%; } #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; }
</style>
<script id="others_zepto_10rc1" type="text/javascript" class="library" src="/js/sandbox/other/zepto.min.js"></script>
</head>
<body>
<header id="header">
<h1>
Header</h1>
</header>
<div id="body">
<div id="wrapper">
body
<ul>
<li>Pretty row 1</li>
<li>Pretty row 2</li>
<li>Pretty row 3</li>
<li>Pretty row 4</li>
<li>
<input type="text"></li>
<li>Pretty row 6</li>
<li>Pretty row 7</li>
<li>Pretty row 8</li>
<li>
<input type="checkbox"></li>
<li>Pretty row 10</li>
<li>Pretty row 11</li>
<li>Pretty row 12</li>
<li>
<input type="radio"></li>
<li>Pretty row 14</li>
<li>Pretty row 15</li>
<li>Pretty row 16</li>
<li>
<textarea></textarea></li>
<li>Pretty row 18</li>
<li>Pretty row 19</li>
<li>Pretty row 20</li>
<li>
<select>
<option>option</option>
</select></li>
</ul>
<hr />
<ul> <li>Pretty row 41</li>
<li>Pretty row 42</li>
<li>Pretty row 43</li>
<li>Pretty row 44</li>
<li>Pretty row 45</li>
<li>Pretty row 46</li>
<li>Pretty row 47</li>
<li>Pretty row 48</li>
<li>Pretty row 49</li>
<li>Pretty row 50</li>
</ul>
</div>
</div>
<footer>
<h1>
Footer</h1>
</footer>
<script src="zepto.js" type="text/javascript"></script>
<script type="text/javascript">
var Scroll = function (opts) {
opts = opts || {};
//检测设备事件支持,确定使用鼠标事件或者touch事件
this._checkEventCompatibility();
this._setBaseParam(opts);
this._addEvent(); this._initScrollBar();
}; Scroll.prototype = {
constructor: Scroll,
//检测设备事件兼容
_checkEventCompatibility: function () {
var isTouch = 'ontouchstart' in document.documentElement; this.start = isTouch ? 'touchstart' : 'mousedown';
this.move = isTouch ? 'touchmove' : 'mousemove';
this.end = isTouch ? 'touchend' : 'mouseup';
this.startFn;
this.moveFn;
this.endFn;
},
//基本参数设置
_setBaseParam: function (opts) {
this.timeGap = 0; //时间间隔
this.touchTime = 0; //开始时间
this.isMoveing = false; //是否正在移动
this.moveState = 'up'; //移动状态,up right down left
this.oTop = 0; //拖动前的top值
this.curTop = 0; //当前容器top
this.mouseY = 0; //鼠标第一次点下时相对父容器的位置
this.animateParam = opts.animateParam || [10, 8, 6, 5, 4, 3, 2, 1, 0, 0, 0]; //动画参数
this.cooling = true; //是否处于冷却时间
this.steplen = 25; //动画步长 this.wrapper = opts.wrapper || $('body');
this.dragEl = opts.body;
this.wrapper.css({ 'position': 'absolute', 'overflow': 'hidden' });
this.dragEl.css('position', 'absolute');
this.wrapper.append(this.dragEl);
},
_initScrollBar: function () {
if (!this.dragHeight) {
this.dragHeight = this.dragEl.offset().height; //拖动元素高度
this.wrapperHeight = this.wrapper.offset().height;
}
//滚动条缩放比例
this.scrollProportion = this.wrapperHeight / this.dragHeight;
this.isNeedScrollBar = true;
//该种情况无需滚动条
if (this.scrollProportion >= 1) {
this.isNeedScrollBar = false; ;
return false;
}
//滚动条
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>');
this.wrapper.append(this.scrollBar);
this.scrollHeight = parseInt(this.scrollProportion * this.wrapperHeight);
this.scrollBar.css('height', this.scrollHeight);
},
_setScrollTop: function (top, duration) {
//滚动条高度
if (this.isNeedScrollBar) {
top = this._getResetData(top).sTop;
top = top < 0 ? (top + 10) : top; var scrollTop = top * (-1);
if (typeof duration == 'number') {
var _top = parseInt(scrollTop * this.scrollProportion) + 'px';
this.scrollBar.animate({
top: _top,
right: '1px'
}, duration, 'linear'); } else {
this.scrollBar.css('top', parseInt(scrollTop * this.scrollProportion) + 'px');
}
this.scrollBar.css('opacity', '0.8');
}
},
_hideScroll: function () {
if (this.isNeedScrollBar) {
this.scrollBar.animate({ 'opacity': '0.2' });
}
},
_addEvent: function () {
var scope = this;
this.startFn = function (e) {
scope._touchStart.call(scope, e);
};
this.moveFn = function (e) {
scope._touchMove.call(scope, e);
};
this.endFn = function (e) {
scope._touchEnd.call(scope, e);
};
this.dragEl[0].addEventListener(this.start, this.startFn, false);
document.addEventListener(this.move, this.moveFn, false);
document.addEventListener(this.end, this.endFn, false);
},
removeEvent: function () {
this.dragEl[0].removeEventListener(this.start, this.startFn);
document.removeEventListener(this.move, this.moveFn);
document.removeEventListener(this.end, this.endFn);
},
_touchStart: function (e) {
var scope = this;
if (this.isMoveing) { e.preventDefault(); return false; }
//非运动情况关闭冷却时间
this.cooling = false;
this.touchTime = e.timeStamp;
pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e);
var top = parseFloat(this.dragEl.css('top')) || 0;
this.mouseY = pos.top - top;
},
_touchMove: function (e) {
if (this.cooling) { e.preventDefault(); return false; } this.isMoveing = true; e.preventDefault();
var pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e); //防止点击时候跳动
if (Math.abs((pos.top - this.mouseY) - this.curTop) < 10) { e.preventDefault(); return false; } //先获取相对容器的位置,在将两个鼠标位置相减
this.curTop = pos.top - this.mouseY; var resetData = this._getResetData(this.curTop);
if (resetData.needReset) {
this.curTop = this._resetEdge(this.curTop);
} this.dragEl.css('top', this.curTop + 'px');
this._setScrollTop(this.curTop);
e.preventDefault(); },
_touchEnd: function (e) {
if (this.cooling) { e.preventDefault(); return false; }
if (Math.abs(this.oTop - this.curTop) < 10) { e.preventDefault(); return false; }
//一次动作结束,开启冷却时间
this.cooling = true;
var scope = this;
this.timeGap = e.timeStamp - this.touchTime;
var flag = this.oTop < this.curTop ? 1 : -1; //判断是向上还是向下滚动
this.moveState = flag > 0 ? 'up' : 'down'; var step = parseInt(this.timeGap / 10 - 10);
step = step > 0 ? step : 0;
var speed = this.animateParam[step] || 0;
var increment = speed * this.steplen * flag;
var top = this.curTop;
top += increment; var resetData = this._getResetData(top);
if (resetData.needReset) {
top = this._resetEdge(top);
speed = 0;
} //!!!此处动画可能导致数据不同步,后期改造需要加入冷却时间
if (this.oTop != this.curTop && this.curTop != top) {
var duration = 100 + (speed * 20);
top += increment;
this.dragEl.animate({
top: top + 'px'
}, duration, 'linear', function () {
scope.reset.call(scope, top); });
this._setScrollTop(top, duration);
} else {
this.isMoveing = false;
this.oTop = top;
this.reset(top);
this.cooling = false; //关闭冷却时间
}
this._hideScroll();
e.preventDefault();
},
_resetEdge: function (top) {
var h1 = parseInt(this.wrapperHeight / 3);
var h2 = parseInt(this.dragHeight * (-1) + this.wrapperHeight * (2 / 3));
if (top > 0 && top > h1) top = h1;
if (top < 0 && top < h2) top = h2;
return top;
},
_getResetData: function (top) {
var needReset = false;
var sTop = top;
if (top < (-1) * (this.dragHeight - this.wrapperHeight)) { top = (-1) * (this.dragHeight - this.wrapperHeight); needReset = true; }
if (top > 0) { top = 0; needReset = true; } return {
top: top,
sTop: sTop,
needReset: needReset
};
},
//超出限制后位置还原
reset: function (top) {
var scope = this;
var needReset = this._getResetData(top).needReset;
var top = this._getResetData(top).top; if (needReset) {
scope.dragEl.animate({
top: top + 'px'
}, 50, 'linear', function () {
scope._reset(top);
scope._setScrollTop(top); });
} else {
scope._reset(top);
}
},
_reset: function (top) {
this.oTop = top;
this.curTop = top;
this.isMoveing = false;
this.cooling = false; //关闭冷却时间
},
//获取鼠标信息
getMousePos: function (event) {
var top, left;
top = Math.max(document.body.scrollTop, document.documentElement.scrollTop);
left = Math.max(document.body.scrollLeft, document.documentElement.scrollLeft);
return {
top: top + event.clientY,
left: left + event.clientX
};
}
};
new Scroll({ wrapper: $('#body'), body: $('#wrapper') });
</script>
</body>
</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手机对比)

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style type="text/css">
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; }
body { font: normal 14px/1.5 "Arial" , "Lucida Grande" ,Verdana, "Microsoft YaHei" , "hei"; -webkit-font-smoothing: antialiased; color: #000; background: #ccc; }
header { position: absolute; top: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
footer { position: absolute; bottom: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
h1 { display: block; font-size: 2em; font-weight: bold; font-weight: 500; text-align: center; color: White; } #body { position: absolute; top: 50px; bottom: 50px; background: #fff; width: 100%; overflow: hidden; }
#wrapper { width: 100%; } #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; }
</style>
<script id="others_zepto_10rc1" type="text/javascript" class="library" src="/js/sandbox/other/zepto.min.js"></script>
</head>
<body>
<header id="header">
<h1>
Header</h1>
</header>
<div id="body">
<div id="wrapper">
body
<ul>
<li>Pretty row 1</li>
<li>Pretty row 2</li>
<li>Pretty row 3</li>
<li>Pretty row 4</li>
<li>
<input type="text"></li>
<li>Pretty row 6</li>
<li>Pretty row 7</li>
<li>Pretty row 8</li>
<li>
<input type="checkbox"></li>
<li>Pretty row 10</li>
<li>Pretty row 11</li>
<li>Pretty row 12</li>
<li>
<input type="radio"></li>
<li>Pretty row 14</li>
<li>Pretty row 15</li>
<li>Pretty row 16</li>
<li>
<textarea></textarea></li>
<li>Pretty row 18</li>
<li>Pretty row 19</li>
<li>Pretty row 20</li>
<li>
<select>
<option>option</option>
</select></li>
</ul>
<hr />
<ul> <li>Pretty row 41</li>
<li>Pretty row 42</li>
<li>Pretty row 43</li>
<li>Pretty row 44</li>
<li>Pretty row 45</li>
<li>Pretty row 46</li>
<li>Pretty row 47</li>
<li>Pretty row 48</li>
<li>Pretty row 49</li>
<li>Pretty row 50</li>
</ul>
</div>
</div>
<footer>
<h1>
Footer</h1>
</footer>
<script src="zepto.js" type="text/javascript"></script>
<script type="text/javascript">
var Scroll = function (opts) {
opts = opts || {};
//检测设备事件支持,确定使用鼠标事件或者touch事件
this._checkEventCompatibility();
this._setBaseParam(opts);
this._addEvent(); this._initScrollBar();
}; Scroll.prototype = {
constructor: Scroll,
//检测设备事件兼容
_checkEventCompatibility: function () {
var isTouch = 'ontouchstart' in document.documentElement;
isTouch = true; this.start = isTouch ? 'touchstart' : 'mousedown';
this.move = isTouch ? 'touchmove' : 'mousemove';
this.end = isTouch ? 'touchend' : 'mouseup';
this.startFn;
this.moveFn;
this.endFn;
},
//基本参数设置
_setBaseParam: function (opts) {
this.timeGap = 0; //时间间隔
this.touchTime = 0; //开始时间
this.isMoveing = false; //是否正在移动
this.moveState = 'up'; //移动状态,up right down left
this.oTop = 0; //拖动前的top值
this.curTop = 0; //当前容器top
this.mouseY = 0; //鼠标第一次点下时相对父容器的位置
this.animateParam = opts.animateParam || [10, 8, 6, 5, 4, 3, 2, 1, 0, 0, 0]; //动画参数
this.cooling = true; //是否处于冷却时间
this.steplen = 25; //动画步长 this.wrapper = opts.wrapper || $('body');
this.dragEl = opts.body;
this.wrapper.css({ 'position': 'absolute', 'overflow': 'hidden' });
this.dragEl.css('position', 'absolute');
this.wrapper.append(this.dragEl);
},
_initScrollBar: function () {
if (!this.dragHeight) {
this.dragHeight = this.dragEl.offset().height; //拖动元素高度
this.wrapperHeight = this.wrapper.offset().height;
}
//滚动条缩放比例
this.scrollProportion = this.wrapperHeight / this.dragHeight;
this.isNeedScrollBar = true;
//该种情况无需滚动条
if (this.scrollProportion >= 1) {
this.isNeedScrollBar = false; ;
return false;
}
//滚动条
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>');
this.wrapper.append(this.scrollBar);
this.scrollHeight = parseInt(this.scrollProportion * this.wrapperHeight);
this.scrollBar.css('height', this.scrollHeight);
},
_setScrollTop: function (top, duration) {
//滚动条高度
if (this.isNeedScrollBar) {
top = this._getResetData(top).sTop;
top = top < 0 ? (top + 10) : top; var scrollTop = top * (-1);
if (typeof duration == 'number') {
var _top = parseInt(scrollTop * this.scrollProportion) + 'px';
this.scrollBar.animate({
'-webkit-transform': 'translate3d(0, ' + _top + ', 0)'
}, duration, 'linear'); } else {
var _st = parseInt(scrollTop * this.scrollProportion)
this.scrollBar.css('-webkit-transform', 'translate3d(0, ' + _st + 'px, 0)');
}
this.scrollBar.css('opacity', '0.8');
}
},
_hideScroll: function () {
if (this.isNeedScrollBar) {
this.scrollBar.css({ 'opacity': '0.2' });
}
},
_addEvent: function () {
var scope = this;
this.startFn = function (e) {
scope._touchStart.call(scope, e);
};
this.moveFn = function (e) {
scope._touchMove.call(scope, e);
};
this.endFn = function (e) {
scope._touchEnd.call(scope, e);
};
this.dragEl[0].addEventListener(this.start, this.startFn, false);
document.addEventListener(this.move, this.moveFn, false);
document.addEventListener(this.end, this.endFn, false);
},
removeEvent: function () {
this.dragEl[0].removeEventListener(this.start, this.startFn);
document.removeEventListener(this.move, this.moveFn);
document.removeEventListener(this.end, this.endFn);
},
_touchStart: function (e) {
var scope = this;
if (this.isMoveing) { e.preventDefault(); return false; }
//非运动情况关闭冷却时间
this.cooling = false;
this.touchTime = e.timeStamp;
pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e);
// var top = parseFloat(this.dragEl.css('top')) || 0;
var top = this._cssTranslate(this.dragEl);
this.mouseY = pos.top - top;
},
_touchMove: function (e) {
if (this.cooling) { e.preventDefault(); return false; } this.isMoveing = true; var pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e); //防止点击时候跳动
if (Math.abs((pos.top - this.mouseY) - this.curTop) < 10) { e.preventDefault(); return false; } //先获取相对容器的位置,在将两个鼠标位置相减
this.curTop = pos.top - this.mouseY; var resetData = this._getResetData(this.curTop);
if (resetData.needReset) {
this.curTop = this._resetEdge(this.curTop);
} // this.dragEl.css('top', this.curTop + 'px');
this._cssTranslate(this.dragEl, this.curTop); this._setScrollTop(this.curTop);
e.preventDefault(); },
_touchEnd: function (e) {
if (this.cooling) { e.preventDefault(); return false; }
if (Math.abs(this.oTop - this.curTop) < 10) { e.preventDefault(); return false; }
//一次动作结束,开启冷却时间
this.cooling = true;
var scope = this;
this.timeGap = e.timeStamp - this.touchTime;
var flag = this.oTop < this.curTop ? 1 : -1; //判断是向上还是向下滚动
this.moveState = flag > 0 ? 'up' : 'down'; var step = parseInt(this.timeGap / 10 - 10);
step = step > 0 ? step : 0;
var speed = this.animateParam[step] || 0;
var increment = speed * this.steplen * flag;
var top = this.curTop;
top += increment; var resetData = this._getResetData(top);
if (resetData.needReset) {
top = this._resetEdge(top);
speed = 0;
} //!!!此处动画可能导致数据不同步,后期改造需要加入冷却时间
if (this.oTop != this.curTop && this.curTop != top) {
var duration = 100 + (speed * 20);
top += increment;
this.dragEl.animate({
'-webkit-transform': 'translate3d(0, ' + top + 'px, 0)'
}, duration, 'linear', function () {
scope.reset.call(scope, top); });
this._setScrollTop(top, duration);
} else {
this.isMoveing = false;
this.oTop = top;
this.reset(top);
this.cooling = false; //关闭冷却时间
}
this._hideScroll();
e.preventDefault();
},
_resetEdge: function (top) {
var h1 = parseInt(this.wrapperHeight / 3);
var h2 = parseInt(this.dragHeight * (-1) + this.wrapperHeight * (2 / 3));
if (top > 0 && top > h1) top = h1;
if (top < 0 && top < h2) top = h2;
return top;
},
_getResetData: function (top) {
var needReset = false;
var sTop = top;
if (top < (-1) * (this.dragHeight - this.wrapperHeight)) { top = (-1) * (this.dragHeight - this.wrapperHeight); needReset = true; }
if (top > 0) { top = 0; needReset = true; } return {
top: top,
sTop: sTop,
needReset: needReset
};
},
//超出限制后位置还原
reset: function (top) {
var scope = this;
var needReset = this._getResetData(top).needReset;
var top = this._getResetData(top).top; if (needReset) {
scope.dragEl.animate({
'-webkit-transform': 'translate3d(0, ' + top + 'px, 0)' }, 50, 'linear', function () {
scope._reset(top);
scope._setScrollTop(top); });
} else {
scope._reset(top);
}
},
_reset: function (top) {
this.oTop = top;
this.curTop = top;
this.isMoveing = false;
this.cooling = false; //关闭冷却时间
},
//暂时仅用于,操作Y值
_cssTranslate: function (el, y) {
if (!el) return 0;
if (typeof y == 'number') {
el.css('-webkit-transform', 'translate3d(0, ' + y + 'px, 0)');
}
var data = /\((.*)\)/.exec(el.css('-webkit-transform'));
if (data && typeof data[1] == 'string') data = data[1].split(',');
if (data && typeof data[1] == 'string') return parseInt(data[1]);
return 0;
},
//获取鼠标信息
getMousePos: function (event) {
var top, left;
top = Math.max(document.body.scrollTop, document.documentElement.scrollTop);
left = Math.max(document.body.scrollLeft, document.documentElement.scrollLeft);
return {
top: top + event.clientY,
left: left + event.clientX
};
}
};
new Scroll({ wrapper: $('#body'), body: $('#wrapper') });
</script>
</body>
</html>

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

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style type="text/css">
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; }
body { font: normal 14px/1.5 "Arial" , "Lucida Grande" ,Verdana, "Microsoft YaHei" , "hei"; -webkit-font-smoothing: antialiased; color: #000; background: #ccc; }
header { position: absolute; top: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
footer { position: absolute; bottom: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
h1 { display: block; font-size: 2em; font-weight: bold; font-weight: 500; text-align: center; color: White; } #body { position: absolute; top: 50px; bottom: 50px; background: #fff; width: 100%; overflow: hidden; }
#wrapper { width: 100%; } #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; }
</style>
<script id="others_zepto_10rc1" type="text/javascript" class="library" src="/js/sandbox/other/zepto.min.js"></script>
</head>
<body>
<header id="header">
<h1>
Header</h1>
</header>
<div id="body">
<div id="wrapper">
body
<ul>
<li>Pretty row 1</li>
<li>Pretty row 2</li>
<li>Pretty row 3</li>
<li>Pretty row 4</li>
<li>
<input type="text"></li>
<li>Pretty row 6</li>
<li>Pretty row 7</li>
<li>Pretty row 8</li>
<li>
<input type="checkbox"></li>
<li>Pretty row 10</li>
<li>Pretty row 11</li>
<li>Pretty row 12</li>
<li>
<input type="radio"></li>
<li>Pretty row 14</li>
<li>Pretty row 15</li>
<li>Pretty row 16</li>
<li>
<textarea></textarea></li>
<li>Pretty row 18</li>
<li>Pretty row 19</li>
<li>Pretty row 20</li>
<li>
<select>
<option>option</option>
</select></li>
</ul>
<hr />
<ul> <li>Pretty row 41</li>
<li>Pretty row 42</li>
<li>Pretty row 43</li>
<li>Pretty row 44</li>
<li>Pretty row 45</li>
<li>Pretty row 46</li>
<li>Pretty row 47</li>
<li>Pretty row 48</li>
<li>Pretty row 49</li>
<li>Pretty row 50</li>
</ul>
</div>
</div>
<footer>
<h1>
Footer</h1>
</footer>
<script src="zepto.js" type="text/javascript"></script>
<script type="text/javascript">
var Scroll = function (opts) {
opts = opts || {};
//检测设备事件支持,确定使用鼠标事件或者touch事件
this._checkEventCompatibility();
this._setBaseParam(opts);
this._addEvent(); this._initScrollBar();
}; Scroll.prototype = {
constructor: Scroll,
//检测设备事件兼容
_checkEventCompatibility: function () {
var isTouch = 'ontouchstart' in document.documentElement;
// isTouch = true; this.start = isTouch ? 'touchstart' : 'mousedown';
this.move = isTouch ? 'touchmove' : 'mousemove';
this.end = isTouch ? 'touchend' : 'mouseup';
this.startFn;
this.moveFn;
this.endFn;
},
//基本参数设置
_setBaseParam: function (opts) {
this.timeGap = 0; //时间间隔
this.touchTime = 0; //开始时间
this.isMoveing = false; //是否正在移动
this.moveState = 'up'; //移动状态,up right down left
this.oTop = 0; //拖动前的top值
this.curTop = 0; //当前容器top
this.mouseY = 0; //鼠标第一次点下时相对父容器的位置
this.animateParam = opts.animateParam || [10, 8, 6, 5, 4, 3, 2, 1, 0, 0, 0]; //动画参数
this.cooling = true; //是否处于冷却时间
this.steplen = 25; //动画步长 this.wrapper = opts.wrapper || $('body');
this.dragEl = opts.body;
this.wrapper.css({ 'position': 'absolute', 'overflow': 'hidden' });
this.dragEl.css('position', 'absolute');
this.wrapper.append(this.dragEl);
},
_initScrollBar: function () {
if (!this.dragHeight) {
this.dragHeight = this.dragEl.offset().height; //拖动元素高度
this.wrapperHeight = this.wrapper.offset().height;
}
//滚动条缩放比例
this.scrollProportion = this.wrapperHeight / this.dragHeight;
this.isNeedScrollBar = true;
//该种情况无需滚动条
if (this.scrollProportion >= 1) {
this.isNeedScrollBar = false; ;
return false;
}
//滚动条
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>');
this.wrapper.append(this.scrollBar);
this.scrollHeight = parseInt(this.scrollProportion * this.wrapperHeight);
this.scrollBar.css('height', this.scrollHeight);
},
_setScrollTop: function (top, duration) {
//滚动条高度
if (this.isNeedScrollBar) {
top = this._getResetData(top).sTop;
top = top < 0 ? (top + 10) : top; var scrollTop = top * (-1);
if (typeof duration == 'number') {
var _top = parseInt(scrollTop * this.scrollProportion) + 'px';
this.scrollBar.animate({
'-webkit-transform': 'translate3d(0, ' + _top + ', 0)'
}, duration, 'linear'); } else {
var _st = parseInt(scrollTop * this.scrollProportion)
this.scrollBar.css('-webkit-transform', 'translate3d(0, ' + _st + 'px, 0)');
}
this.scrollBar.css('opacity', '0.8');
}
},
_hideScroll: function () {
if (this.isNeedScrollBar) {
this.scrollBar.css({ 'opacity': '0.2' });
}
},
_addEvent: function () {
var scope = this;
this.startFn = function (e) {
scope._touchStart.call(scope, e);
};
this.moveFn = function (e) {
scope._touchMove.call(scope, e);
};
this.endFn = function (e) {
scope._touchEnd.call(scope, e);
};
this.dragEl[0].addEventListener(this.start, this.startFn, false);
document.addEventListener(this.move, this.moveFn, false);
document.addEventListener(this.end, this.endFn, false);
},
removeEvent: function () {
this.dragEl[0].removeEventListener(this.start, this.startFn);
document.removeEventListener(this.move, this.moveFn);
document.removeEventListener(this.end, this.endFn);
},
_touchStart: function (e) {
var scope = this;
window.dragEl = this.dragEl; if (this.isMoveing) { var el = this.dragEl[0];
var computedStyle = document.defaultView.getComputedStyle(el, null);
// computedStyle.getPropertyValue("width"); var top = 0;
var data = /\((.*)\)/.exec(computedStyle.getPropertyValue("-webkit-transform"));
if (typeof data == 'object') data = data[1].split(',');
if (typeof data == 'object') top = parseInt(data[5]);
console.log(top); this.dragEl.css('-webkit-transition', '-webkit-transform 0s linear');
this.dragEl.css('transition', '-webkit-transform 0s linear'); this._cssTranslate(this.dragEl, top);
this._setScrollTop(top); this.isMoveing = false;
e.preventDefault(); return false;
} this.clickEl = e.target; //非运动情况关闭冷却时间
this.cooling = false;
this.touchTime = e.timeStamp;
pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e);
// var top = parseFloat(this.dragEl.css('top')) || 0;
var top = this._cssTranslate(this.dragEl);
this.mouseY = pos.top - top;
},
_touchMove: function (e) {
if (this.cooling) { e.preventDefault(); return false; } this.isMoveing = true; var pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e); //防止点击时候跳动
if (Math.abs((pos.top - this.mouseY) - this.curTop) < 10) { e.preventDefault(); return false; } //先获取相对容器的位置,在将两个鼠标位置相减
this.curTop = pos.top - this.mouseY; var resetData = this._getResetData(this.curTop);
if (resetData.needReset) {
this.curTop = this._resetEdge(this.curTop);
} // this.dragEl.css('top', this.curTop + 'px');
this._cssTranslate(this.dragEl, this.curTop); this._setScrollTop(this.curTop);
e.preventDefault(); },
_touchEnd: function (e) { if (this._needFocus(this.clickEl)) {
$(this.clickEl).focus();
return;
} if (this.cooling) { e.preventDefault(); return false; }
if (Math.abs(this.oTop - this.curTop) < 10) { e.preventDefault(); return false; }
//一次动作结束,开启冷却时间
this.cooling = true;
var scope = this;
this.timeGap = e.timeStamp - this.touchTime;
var flag = this.oTop < this.curTop ? 1 : -1; //判断是向上还是向下滚动
this.moveState = flag > 0 ? 'up' : 'down'; var step = parseInt(this.timeGap / 10 - 10);
step = step > 0 ? step : 0;
var speed = this.animateParam[step] || 0;
var increment = speed * this.steplen * flag;
var top = this.curTop;
top += increment; var resetData = this._getResetData(top);
if (resetData.needReset) {
top = this._resetEdge(top);
speed = 0;
} speed = 1000; //!!!此处动画可能导致数据不同步,后期改造需要加入冷却时间
if (this.oTop != this.curTop && this.curTop != top) {
var duration = 100 + (speed * 20);
top += increment;
this.dragEl.animate({
'-webkit-transform': 'translate3d(0, ' + top + 'px, 0)'
}, duration, 'linear', function () {
scope.reset.call(scope, top); });
this._setScrollTop(top, duration);
} else {
this.isMoveing = false;
this.oTop = top;
this.reset(top);
this.cooling = false; //关闭冷却时间
}
this._hideScroll();
e.preventDefault();
},
_resetEdge: function (top) {
var h1 = parseInt(this.wrapperHeight / 3);
var h2 = parseInt(this.dragHeight * (-1) + this.wrapperHeight * (2 / 3));
if (top > 0 && top > h1) top = h1;
if (top < 0 && top < h2) top = h2;
return top;
},
_getResetData: function (top) {
var needReset = false;
var sTop = top;
if (top < (-1) * (this.dragHeight - this.wrapperHeight)) { top = (-1) * (this.dragHeight - this.wrapperHeight); needReset = true; }
if (top > 0) { top = 0; needReset = true; } return {
top: top,
sTop: sTop,
needReset: needReset
};
},
//超出限制后位置还原
reset: function (top) {
var scope = this;
var needReset = this._getResetData(top).needReset;
var top = this._getResetData(top).top; if (needReset) {
scope.dragEl.animate({
'-webkit-transform': 'translate3d(0, ' + top + 'px, 0)' }, 50, 'linear', function () {
scope._reset(top);
scope._setScrollTop(top); });
} else {
scope._reset(top);
}
},
_reset: function (top) {
this.oTop = top;
this.curTop = top;
this.isMoveing = false;
this.cooling = false; //关闭冷却时间
},
//暂时仅用于,操作Y值
_cssTranslate: function (el, y) {
if (!el) return 0;
if (typeof y == 'number') {
el.css('-webkit-transform', 'translate3d(0, ' + y + 'px, 0)');
}
var data = /\((.*)\)/.exec(el.css('-webkit-transform'));
if (data && typeof data[1] == 'string') data = data[1].split(',');
if (data && typeof data[1] == 'string') return parseInt(data[1]);
return 0;
},
_needFocus: function (el) {
switch (el.nodeName.toLowerCase()) {
case 'textarea':
case 'select':
return true;
case 'input':
switch (el.type) {
case 'button':
case 'checkbox':
case 'file':
case 'image':
case 'radio':
case 'submit':
return false;
}
return !el.disabled && !el.readOnly;
default:
return (/\bneedfocus\b/).test(el.className);
}
},
//获取鼠标信息
getMousePos: function (event) {
var top, left;
top = Math.max(document.body.scrollTop, document.documentElement.scrollTop);
left = Math.max(document.body.scrollLeft, document.documentElement.scrollLeft);
return {
top: top + event.clientY,
left: left + event.clientX
};
}
};
new Scroll({ wrapper: $('#body'), body: $('#wrapper') });
</script>
</body>
</html>

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

文本获取焦点

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

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

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

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

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style type="text/css">
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; }
body { font: normal 14px/1.5 "Arial" , "Lucida Grande" ,Verdana, "Microsoft YaHei" , "hei"; -webkit-font-smoothing: antialiased; color: #000; background: #ccc; }
header { position: absolute; top: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
footer { position: absolute; bottom: 0; left: 0; width: 100%; height: 48px; background-color: #1491c5; }
h1 { display: block; font-size: 2em; font-weight: bold; font-weight: 500; text-align: center; color: White; } #body { position: absolute; top: 50px; bottom: 50px; background: #fff; width: 100%; overflow: hidden; }
#wrapper { width: 100%; } #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; }
</style>
<script id="others_zepto_10rc1" type="text/javascript" class="library" src="/js/sandbox/other/zepto.min.js"></script>
</head>
<body>
<header id="header">
<h1>
Header</h1>
</header>
<div id="body">
<div id="wrapper">
body
<ul>
<li>Pretty row 1</li>
<li>Pretty row 2</li>
<li>Pretty row 3</li>
<li>Pretty row 4</li>
<li>
<input type="text"></li>
<li>Pretty row 6</li>
<li>Pretty row 7</li>
<li>Pretty row 8</li>
<li>
<input type="checkbox"></li>
<li>Pretty row 10</li>
<li>Pretty row 11</li>
<li>Pretty row 12</li>
<li>
<input type="radio"></li>
<li>Pretty row 14</li>
<li>Pretty row 15</li>
<li>Pretty row 16</li>
<li>
<textarea></textarea></li>
<li>Pretty row 18</li>
<li>Pretty row 19</li>
<li>Pretty row 20</li>
<li>
<select>
<option>option</option>
</select></li>
</ul>
<hr />
<ul> <li>Pretty row 41</li>
<li>Pretty row 42</li>
<li>Pretty row 43</li>
<li>Pretty row 44</li>
<li>Pretty row 45</li>
<li>Pretty row 46</li>
<li>Pretty row 47</li>
<li>Pretty row 48</li>
<li>Pretty row 49</li>
<li>Pretty row 50</li>
</ul>
</div>
</div>
<footer>
<h1>
Footer</h1>
</footer>
<script src="zepto.js" type="text/javascript"></script>
<script type="text/javascript">
var Scroll = function (opts) {
opts = opts || {};
//检测设备事件支持,确定使用鼠标事件或者touch事件
this._checkEventCompatibility();
this._setBaseParam(opts);
this._addEvent(); this._initScrollBar();
}; Scroll.prototype = {
constructor: Scroll,
//检测设备事件兼容
_checkEventCompatibility: function () {
var isTouch = 'ontouchstart' in document.documentElement; this.start = isTouch ? 'touchstart' : 'mousedown';
this.move = isTouch ? 'touchmove' : 'mousemove';
this.end = isTouch ? 'touchend' : 'mouseup';
this.startFn;
this.moveFn;
this.endFn;
},
//基本参数设置
_setBaseParam: function (opts) {
this.timeGap = 0; //时间间隔
this.touchTime = 0; //开始时间
this.isMoveing = false; //是否正在移动
this.moveState = 'up'; //移动状态,up right down left
this.oTop = 0; //拖动前的top值
this.curTop = 0; //当前容器top
this.mouseY = 0; //鼠标第一次点下时相对父容器的位置
this.animateParam = opts.animateParam || [10, 8, 6, 5, 4, 3, 2, 1, 0, 0, 0]; //动画参数
this.cooling = true; //是否处于冷却时间
this.steplen = 25; //动画步长 this.wrapper = opts.wrapper || $('body');
this.dragEl = opts.body;
this.wrapper.css({ 'position': 'absolute', 'overflow': 'hidden' });
this.dragEl.css('position', 'absolute');
this.wrapper.append(this.dragEl);
},
_initScrollBar: function () {
if (!this.dragHeight) {
this.dragHeight = this.dragEl.offset().height; //拖动元素高度
this.wrapperHeight = this.wrapper.offset().height;
}
//滚动条缩放比例
this.scrollProportion = this.wrapperHeight / this.dragHeight;
this.isNeedScrollBar = true;
//该种情况无需滚动条
if (this.scrollProportion >= 1) {
this.isNeedScrollBar = false; ;
return false;
}
//滚动条
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>');
this.wrapper.append(this.scrollBar);
this.scrollHeight = parseInt(this.scrollProportion * this.wrapperHeight);
this.scrollBar.css('height', this.scrollHeight);
},
_setScrollTop: function (top, duration) {
//滚动条高度
if (this.isNeedScrollBar) {
top = this._getResetData(top).sTop;
top = top < 0 ? (top + 10) : top; var scrollTop = top * (-1);
if (typeof duration == 'number') {
var _top = parseInt(scrollTop * this.scrollProportion) + 'px';
this.scrollBar.animate({
'-webkit-transform': 'translate3d(0, ' + _top + ', 0)'
}, duration, 'linear'); } else {
var _st = parseInt(scrollTop * this.scrollProportion)
this.scrollBar.css('-webkit-transform', 'translate3d(0, ' + _st + 'px, 0)');
}
this.scrollBar.css('opacity', '0.8');
}
},
_hideScroll: function () {
if (this.isNeedScrollBar) {
this.scrollBar.css({ 'opacity': '0.2' });
}
},
_addEvent: function () {
var scope = this;
this.startFn = function (e) {
scope._touchStart.call(scope, e);
};
this.moveFn = function (e) {
scope._touchMove.call(scope, e);
};
this.endFn = function (e) {
scope._touchEnd.call(scope, e);
};
this.dragEl[0].addEventListener(this.start, this.startFn, false);
document.addEventListener(this.move, this.moveFn, false);
document.addEventListener(this.end, this.endFn, false);
},
removeEvent: function () {
this.dragEl[0].removeEventListener(this.start, this.startFn);
document.removeEventListener(this.move, this.moveFn);
document.removeEventListener(this.end, this.endFn);
},
_touchStart: function (e) {
var scope = this;
if (this.isMoveing) { e.preventDefault(); return false; } this.clickEl = e.target; //非运动情况关闭冷却时间
this.cooling = false;
this.touchTime = e.timeStamp;
pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e);
// var top = parseFloat(this.dragEl.css('top')) || 0;
var top = this._cssTranslate(this.dragEl);
this.mouseY = pos.top - top;
},
_touchMove: function (e) {
if (this.cooling) { e.preventDefault(); return false; } this.isMoveing = true; var pos = this.getMousePos((e.changedTouches && e.changedTouches[0]) || e); //防止点击时候跳动
if (Math.abs((pos.top - this.mouseY) - this.curTop) < 10) { e.preventDefault(); return false; } //先获取相对容器的位置,在将两个鼠标位置相减
this.curTop = pos.top - this.mouseY; var resetData = this._getResetData(this.curTop);
if (resetData.needReset) {
this.curTop = this._resetEdge(this.curTop);
} // this.dragEl.css('top', this.curTop + 'px');
this._cssTranslate(this.dragEl, this.curTop); this._setScrollTop(this.curTop);
e.preventDefault(); },
_touchEnd: function (e) { if (this._needFocus(this.clickEl)) {
$(this.clickEl).focus();
return;
} if (this.cooling) { e.preventDefault(); return false; }
if (Math.abs(this.oTop - this.curTop) < 10) { e.preventDefault(); return false; }
//一次动作结束,开启冷却时间
this.cooling = true;
var scope = this;
this.timeGap = e.timeStamp - this.touchTime;
var flag = this.oTop < this.curTop ? 1 : -1; //判断是向上还是向下滚动
this.moveState = flag > 0 ? 'up' : 'down'; var step = parseInt(this.timeGap / 10 - 10);
step = step > 0 ? step : 0;
var speed = this.animateParam[step] || 0;
var increment = speed * this.steplen * flag;
var top = this.curTop;
top += increment; var resetData = this._getResetData(top);
if (resetData.needReset) {
top = this._resetEdge(top);
speed = 0;
} //!!!此处动画可能导致数据不同步,后期改造需要加入冷却时间
if (this.oTop != this.curTop && this.curTop != top) {
var duration = 100 + (speed * 20);
top += increment;
this.dragEl.animate({
'-webkit-transform': 'translate3d(0, ' + top + 'px, 0)'
}, duration, 'linear', function () {
scope.reset.call(scope, top); });
this._setScrollTop(top, duration);
} else {
this.isMoveing = false;
this.oTop = top;
this.reset(top);
this.cooling = false; //关闭冷却时间
}
this._hideScroll();
e.preventDefault();
},
_resetEdge: function (top) {
var h1 = parseInt(this.wrapperHeight / 3);
var h2 = parseInt(this.dragHeight * (-1) + this.wrapperHeight * (2 / 3));
if (top > 0 && top > h1) top = h1;
if (top < 0 && top < h2) top = h2;
return top;
},
_getResetData: function (top) {
var needReset = false;
var sTop = top;
if (top < (-1) * (this.dragHeight - this.wrapperHeight)) { top = (-1) * (this.dragHeight - this.wrapperHeight); needReset = true; }
if (top > 0) { top = 0; needReset = true; } return {
top: top,
sTop: sTop,
needReset: needReset
};
},
//超出限制后位置还原
reset: function (top) {
var scope = this;
var needReset = this._getResetData(top).needReset;
var top = this._getResetData(top).top; if (needReset) {
scope.dragEl.animate({
'-webkit-transform': 'translate3d(0, ' + top + 'px, 0)' }, 50, 'linear', function () {
scope._reset(top);
scope._setScrollTop(top); });
} else {
scope._reset(top);
}
},
_reset: function (top) {
this.oTop = top;
this.curTop = top;
this.isMoveing = false;
this.cooling = false; //关闭冷却时间
},
//暂时仅用于,操作Y值
_cssTranslate: function (el, y) {
if (!el) return 0;
if (typeof y == 'number') {
el.css('-webkit-transform', 'translate3d(0, ' + y + 'px, 0)');
}
var data = /\((.*)\)/.exec(el.css('-webkit-transform'));
if (data && typeof data[1] == 'string') data = data[1].split(',');
if (data && typeof data[1] == 'string') return parseInt(data[1]);
return 0;
},
_needFocus: function (el) {
switch (el.nodeName.toLowerCase()) {
case 'textarea':
case 'select':
return true;
case 'input':
switch (el.type) {
case 'button':
case 'checkbox':
case 'file':
case 'image':
case 'radio':
case 'submit':
return false;
}
return !el.disabled && !el.readOnly;
default:
return (/\bneedfocus\b/).test(el.className);
}
},
//获取鼠标信息
getMousePos: function (event) {
var top, left;
top = Math.max(document.body.scrollTop, document.documentElement.scrollTop);
left = Math.max(document.body.scrollLeft, document.documentElement.scrollLeft);
return {
top: top + event.clientY,
left: left + event.clientX
};
}
};
new Scroll({ wrapper: $('#body'), body: $('#wrapper') });
</script>
</body>
</html>

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

核心代码:

 if (this._needFocus(this.clickEl)) {
$(this.clickEl).focus();
return;
}
_needFocus: function (el) {
switch (el.nodeName.toLowerCase()) {
case 'textarea':
case 'select':
return true;
case 'input':
switch (el.type) {
case 'button':
case 'checkbox':
case 'file':
case 'image':
case 'radio':
case 'submit':
return false;
}
return !el.disabled && !el.readOnly;
default:
return (/\bneedfocus\b/).test(el.className);
}
},

结语

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

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

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,086
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,561
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,410
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,183
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,820
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,903