【iScroll源码学习01】准备阶段
前言
我们昨天初步了解了为什么会出现iScroll:【SPA】移动站点APP化研究之上中下页面的iScroll化(上),然后简单的写了一个demo来模拟iScroll,其中了解到了以下知识点:
① viewport相关知识点(device-width等)
② CSS3硬件加速
③ 如何暂停CSS动画
④ e.preventDefault导致文本不能获取焦点解决方案
......
当然,我们写的demo自然不能和iScroll本身的代码比肩,但是demo过程中我们也大概了解了iScroll代码过程中需要注意的一些问题
于是,今天让我们进入iScroll的学习吧
初探iScroll
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0"> <title>iScroll demo: scrollbars</title> <script type="text/javascript" src="../../build/iscroll.js"></script> <script type="text/javascript"> var myScroll; function loaded () {
myScroll = new IScroll('#wrapper', {
scrollbars: true,
mouseWheel: true,
interactiveScrollbars: true,
shrinkScrollbars: 'scale',
fadeScrollbars: true
});
} document.addEventListener('touchmove', function (e) { e.preventDefault(); }, false); </script> <style type="text/css">
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
} html {
-ms-touch-action: none;
} body,ul,li {
padding: 0;
margin: 0;
border: 0;
} body {
font-size: 12px;
font-family: ubuntu, helvetica, arial;
overflow: hidden; /* this is important to prevent the whole page to bounce */
} #header {
position: absolute;
z-index: 2;
top: 0;
left: 0;
width: 100%;
height: 45px;
line-height: 45px;
background: #CD235C;
padding: 0;
color: #eee;
font-size: 20px;
text-align: center;
font-weight: bold;
} #footer {
position: absolute;
z-index: 2;
bottom: 0;
left: 0;
width: 100%;
height: 48px;
background: #444;
padding: 0;
border-top: 1px solid #444;
} #wrapper {
position: absolute;
z-index: 1;
top: 45px;
bottom: 48px;
left: 0;
width: 100%;
background: #ccc;
overflow: hidden;
} #scroller {
position: absolute;
z-index: 1;
-webkit-tap-highlight-color: rgba(0,0,0,0);
width: 100%;
-webkit-transform: translateZ(0);
-moz-transform: translateZ(0);
-ms-transform: translateZ(0);
-o-transform: translateZ(0);
transform: translateZ(0);
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-text-size-adjust: none;
-moz-text-size-adjust: none;
-ms-text-size-adjust: none;
-o-text-size-adjust: none;
text-size-adjust: none;
} #scroller ul {
list-style: none;
padding: 0;
margin: 0;
width: 100%;
text-align: left;
} #scroller 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 onload="loaded()">
<div id="header">iScroll</div> <div id="wrapper">
<div id="scroller">
<ul>
<li>Pretty row 1</li>
<li>Pretty row 2</li>
<li>Pretty row 3</li>
<li>Pretty row 4</li>
<li>Pretty row 5</li>
<li>Pretty row 6</li>
<li>Pretty row 7</li>
<li>Pretty row 8</li>
<li>Pretty row 9</li>
<li>Pretty row 10</li>
<li>Pretty row 11</li>
<li>Pretty row 12</li>
<li>Pretty row 13</li>
<li>Pretty row 14</li>
<li>Pretty row 15</li>
<li>Pretty row 16</li>
<li>Pretty row 17</li>
<li>Pretty row 18</li>
<li>Pretty row 19</li>
<li>Pretty row 20</li>
<li>Pretty row 21</li>
<li>Pretty row 22</li>
<li>Pretty row 23</li>
<li>Pretty row 24</li>
<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> <li>Pretty row 1</li>
<li>Pretty row 2</li>
<li>Pretty row 3</li>
<li>Pretty row 4</li>
<li>Pretty row 5</li>
<li>Pretty row 6</li>
<li>Pretty row 7</li>
<li>Pretty row 8</li>
<li>Pretty row 9</li>
<li>Pretty row 10</li>
<li>Pretty row 11</li>
<li>Pretty row 12</li>
<li>Pretty row 13</li>
<li>Pretty row 14</li>
<li>Pretty row 15</li>
<li>Pretty row 16</li>
<li>Pretty row 17</li>
<li>Pretty row 18</li>
<li>Pretty row 19</li>
<li>Pretty row 20</li>
<li>Pretty row 21</li>
<li>Pretty row 22</li>
<li>Pretty row 23</li>
<li>Pretty row 24</li>
<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> <li>Pretty row 1</li>
<li>Pretty row 2</li>
<li>Pretty row 3</li>
<li>Pretty row 4</li>
<li>Pretty row 5</li>
<li>Pretty row 6</li>
<li>Pretty row 7</li>
<li>Pretty row 8</li>
<li>Pretty row 9</li>
<li>Pretty row 10</li>
<li>Pretty row 11</li>
<li>Pretty row 12</li>
<li>Pretty row 13</li>
<li>Pretty row 14</li>
<li>Pretty row 15</li>
<li>Pretty row 16</li>
<li>Pretty row 17</li>
<li>Pretty row 18</li>
<li>Pretty row 19</li>
<li>Pretty row 20</li>
<li>Pretty row 21</li>
<li>Pretty row 22</li>
<li>Pretty row 23</li>
<li>Pretty row 24</li>
<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> <li>Pretty row 1</li>
<li>Pretty row 2</li>
<li>Pretty row 3</li>
<li>Pretty row 4</li>
<li>Pretty row 5</li>
<li>Pretty row 6</li>
<li>Pretty row 7</li>
<li>Pretty row 8</li>
<li>Pretty row 9</li>
<li>Pretty row 10</li>
<li>Pretty row 11</li>
<li>Pretty row 12</li>
<li>Pretty row 13</li>
<li>Pretty row 14</li>
<li>Pretty row 15</li>
<li>Pretty row 16</li>
<li>Pretty row 17</li>
<li>Pretty row 18</li>
<li>Pretty row 19</li>
<li>Pretty row 20</li>
<li>Pretty row 21</li>
<li>Pretty row 22</li>
<li>Pretty row 23</li>
<li>Pretty row 24</li>
<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> <div id="footer"></div>
</body>
</html>
http://sandbox.runjs.cn/show/pscjy3a3
下面是他初始化时候的核心代码:
var myScroll;
function loaded () {
myScroll = new IScroll('#wrapper', {
scrollbars: true,
mouseWheel: true,
interactiveScrollbars: true,
shrinkScrollbars: 'scale',
fadeScrollbars: true
});
}
document.addEventListener('touchmove', function (e) { e.preventDefault(); }, false);
真不得不说,这个滑动起来感觉挺不错的,第一感觉有几大特点:
① 顺畅
② 连续拖动会加速滑动
③ 没有BUG :)
看看他的构造函数,然后去网上找一点参数说明(Iscroll应用文档):
hScroll: true, //是否水平滚动
vScroll: true, //是否垂直滚动
x: 0, //滚动水平初始位置
y: 0, //滚动垂直初始位置
snap: true, //值可以为true或是DOM元素的tagname,当为true时,对齐的坐标会根据可滚动的位置和滚动区域计算得到可滑动几页,如果为tagname,则滑动会对齐到元素上
bounce: true, //是否超过实际位置反弹
bounceLock: false, //当内容少于滚动是否可以反弹,这个实际用处不大
momentum: true, //动量效果,拖动惯性
lockDirection: true, //当水平滚动和垂直滚动同时生效时,当拖动开始是否锁定另一边的拖动
useTransform: true, //是否使用CSS形变
useTransition: false, //是否使用CSS变换
topOffset: 0, //已经滚动的基准值(一般情况用不到)
checkDOMChanges: false, //是否自动检测内容变化(这个检测不是很准) //Scrollbar相关参数,通过scrollbar这些参数可以配置iscroll的滚动条,通过scrollbarClass可以自己定义一套滚动条的样式。
hScrollbar: true, //是否显示水平滚动条
vScrollbar: true, //同上垂直滚动条
fixedScrollbar: isAndroid, //对andriod的fixed
hideScrollbar: isIDevice, //是否隐藏滚动条
fadeScrollbar: isIDevice && has3d, //滚动条是否渐隐渐显
scrollbarClass: '', //自定义滚动条的样式名 //Zoom放大相关的参数,通过它,对于一个固定显示图片区域的类似应用,可以非常简单的做到固定滚动,包括两指放大的应用。
zoom: false, //默认是否放大
zoomMin: 1, //放大的最小倍数
zoomMax: 4, //最大倍数
doubleTapZoom: 2, //双触放大几倍
wheelAction: 'scroll', //鼠标滚动行为(还可以是zoom) //自定义Events相关参数
onRefresh: null, //refresh 的回调,关于自身何时调用refresh 后面会继续谈到
onBeforeScrollStart: function(e){ e.preventDefault(); }, //开始滚动前的时间回调,默认是阻止浏览器默认行为
onScrollStart: null, //开始滚动的回调
onBeforeScrollMove: null, //在内容移动前的回调
onScrollMove: null, //内容移动的回调
onBeforeScrollEnd: null, //在滚动结束前的回调
onScrollEnd: null, //在滚动完成后的回调
onTouchEnd: null, //手离开屏幕后的回调
onDestroy: null, //销毁实例的回调
onZoomStart: null, //开始放大前的回调
onZoom: null, //放大的回调
onZoomEnd: null //放大完成后的回调
Iscroll 提供的调用方法 destroy
顾名思义,是用来销毁你实例化的iScroll 实例,包括之前绑定的所有iscroll 事件。 refresh
这个方法非常有用,当你的滚动区域的内容发生改变 或是 滚动区域不正确,都用通过调用refresh 来使得iscroll 重新计算滚动的区域,包括滚动条,来使得iscroll 适合当前的dom。 scrollTo
这个方法接受4个参数 x, y, time, relative x 为移动的x轴坐标,y为移动的y轴坐标, time为移动时间,relative表示是否相对当前位置。 scrollToElement
这个方法实际上是对scrollTo的进一步封装,接受两个参数(el,time),el为需要滚动到的元素引用,time为滚动时间。 scrollToPage
此方法接受三个参数(pageX,pageY,time) 当滚动内容的高宽大于滚动范围时,iscroll 会自动分页,然后就能使用scrollToPage方法滚动到页面。当然,当hscroll 为false 的时候,不能左右滚动。pageX这个参数就失去效果 disable
调用这个方法会立即停止动画滚动,并且把滚动位置还原成0,取消绑定touchmove, touchend、touchcancel事件。 enable
调用这个方法,使得iscroll恢复默认正常状态 stop
立即停止动画 zoom
改变内容的大小倍数,此方法接受4个参数,x,y,scale,time 分别表示的意思为,放大的基准坐标,以及放大倍数,动画时间 isReady
当iscroll 没有处于正在滚动,没有移动过,没有改变大小时,此值为true
功能非常丰富啊,对于应用来说够用了,但是一些功能我这里用不到,就忽略了
功能很好,size为48k,压缩后稍微好一点,将近2000行的代码,作为基础库来说,有点大了,比整个zepto还大
而且整个库的注释写的不好,好像压根就没写......不知道阅读上会不会有障碍,于是我们进入源码
iScroll笔记
requestAnimationFrame
var rAF = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) { window.setTimeout(callback, 1000 / 60); };
这段代码是要做能力检测的,这里来说一下requestAnimationFrame这个东西(参考:http://www.kimhou.com/?p=155)
在jquery中javascript动画是通过定时器(settimeout)实现的,没一个时间点改变一点style,而CSS3后便推出了transition以及animation开始实现动画
我们昨天提到的硬件加速,也是CSS3相关的东西。CSS3动画效率与顺畅度比Js高,所以现在动画开始楚河汉界了
js的好处是可以很好的控制动画状态、css动画带来的性能较高,但是控制度就低一点(是很低)
定时器实现动画
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
</head>
<body>
<div id="el" style="position: absolute;">
Javascript时钟实现动画
</div>
<script src="zepto.js" type="text/javascript"></script>
<script type="text/javascript">
var pos = 0;
var final = 200;
var dir = 0;
var el = $('#el');
function upLeft() {
var left = parseInt(el.css('left')) || 0;
if (left >= final) dir = 1;
if (left <= pos) dir = 0; if (dir == 0) {
left++;
el.css('left', left + 'px');
setTimeout(upLeft);
} else {
left--;
el.css('left', left + 'px');
setTimeout(upLeft);
}
}
upLeft();
</script>
</body>
</html>
效果见下面
这便是使用javascript实现的一个最简单的动画,各位看到了,里面的定时器不停的在运动,性能不差的话我就改名叫素还真了
这个阶段,比较棘手的问题往往在延迟的计算,间隔要短所以动画顺畅,但是浏览器渲染也得耗费时间,这个就要求每次变化留给浏览器的时间够长了(60HZ/75Hz)
所以之前javascript的间隔一般为20左右,这个时候的动画比较流畅,这个数字与浏览器的频率比较接近(1000/60)
function (callback) { window.setTimeout(callback, 1000 / 60); }
但是通过前面对时钟的学习,我们知道settimeout只是将回调函数加入UI线程队列,那么同一时间有多个动画待执行的话,延迟就发生了,效果也会打折扣
这里原来用js实现坦克大战的朋友就会有所体会了
javascript问题解决
CSS的transition与animations的优势在于浏览器知道哪些动画将会发生,所以动画会得到正确的间隔来刷新UI(javascript当然不知道)
于是这里就多了一个方法:RequestAnimationFrame,他可以告诉浏览器自己要执行动画了,于是js的动画事实上得到了优化
RequestAnimationFrame接受一个参数,也就是屏幕重绘前会调用的函数,这个函数用来改变dom样式,这个方法使用有点类似于settimeout
<!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>
</head>
<body> <div id="el" style="position: absolute;">
Javascript时钟实现动画
</div>
<script src="zepto.js" type="text/javascript"></script>
<script type="text/javascript">
var rAF = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function (callback) { window.setTimeout(callback, 1000 / 60); };
var pos = 0;
var final = 200;
var dir = 0;
var el = $('#el');
function upLeft() {
var left = parseInt(el.css('left')) || 0;
if (left >= final) dir = 1;
if (left <= pos) dir = 0; if (dir == 0) {
left++;
el.css('left', left + 'px');
rAF(upLeft);
} else {
left--;
el.css('left', left + 'px');
rAF(upLeft);
}
}
upLeft();
</script>
</body>
</html>
这个动画将会有不一样的感受:
动画效果相关,各位自己去感受,代码各位可以自己调整。如此这个方法其实就是做javascript动画处理优化方案的
PS:尼玛,iScroll第一段就搞了这么久啊这么久
utils
然后iScroll将自己下面会用到的常用操作封装到了这个对象中——utils。
var utils = (function () {
var me = {}; var _elementStyle = document.createElement('div').style;
var _vendor = (function () {
var vendors = ['t', 'webkitT', 'MozT', 'msT', 'OT'],
transform,
i = 0,
l = vendors.length; for ( ; i < l; i++ ) {
transform = vendors[i] + 'ransform';
if ( transform in _elementStyle ) return vendors[i].substr(0, vendors[i].length-1);
} return false;
})(); function _prefixStyle (style) {
if ( _vendor === false ) return false;
if ( _vendor === '' ) return style;
return _vendor + style.charAt(0).toUpperCase() + style.substr(1);
} me.getTime = Date.now || function getTime () { return new Date().getTime(); }; me.extend = function (target, obj) {
for ( var i in obj ) {
target[i] = obj[i];
}
}; me.addEvent = function (el, type, fn, capture) {
el.addEventListener(type, fn, !!capture);
}; me.removeEvent = function (el, type, fn, capture) {
el.removeEventListener(type, fn, !!capture);
}; me.momentum = function (current, start, time, lowerMargin, wrapperSize) {
var distance = current - start,
speed = Math.abs(distance) / time,
destination,
duration,
deceleration = 0.0006; destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 );
duration = speed / deceleration; if ( destination < lowerMargin ) {
destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin;
distance = Math.abs(destination - current);
duration = distance / speed;
} else if ( destination > 0 ) {
destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0;
distance = Math.abs(current) + destination;
duration = distance / speed;
} return {
destination: Math.round(destination),
duration: duration
};
}; var _transform = _prefixStyle('transform'); me.extend(me, {
hasTransform: _transform !== false,
hasPerspective: _prefixStyle('perspective') in _elementStyle,
hasTouch: 'ontouchstart' in window,
hasPointer: navigator.msPointerEnabled,
hasTransition: _prefixStyle('transition') in _elementStyle
}); // This should find all Android browsers lower than build 535.19 (both stock browser and webview)
me.isBadAndroid = /Android/.test(window.navigator.appVersion) && !(/Chrome\/\d/.test(window.navigator.appVersion)); me.extend(me.style = {}, {
transform: _transform,
transitionTimingFunction: _prefixStyle('transitionTimingFunction'),
transitionDuration: _prefixStyle('transitionDuration'),
transitionDelay: _prefixStyle('transitionDelay'),
transformOrigin: _prefixStyle('transformOrigin')
}); me.hasClass = function (e, c) {
var re = new RegExp("(^|\\s)" + c + "(\\s|$)");
return re.test(e.className);
}; me.addClass = function (e, c) {
if ( me.hasClass(e, c) ) {
return;
} var newclass = e.className.split(' ');
newclass.push(c);
e.className = newclass.join(' ');
}; me.removeClass = function (e, c) {
if ( !me.hasClass(e, c) ) {
return;
} var re = new RegExp("(^|\\s)" + c + "(\\s|$)", 'g');
e.className = e.className.replace(re, ' ');
}; me.offset = function (el) {
var left = -el.offsetLeft,
top = -el.offsetTop; // jshint -W084
while (el = el.offsetParent) {
left -= el.offsetLeft;
top -= el.offsetTop;
}
// jshint +W084 return {
left: left,
top: top
};
}; me.preventDefaultException = function (el, exceptions) {
for ( var i in exceptions ) {
if ( exceptions[i].test(el[i]) ) {
return true;
}
} return false;
}; me.extend(me.eventType = {}, {
touchstart: 1,
touchmove: 1,
touchend: 1, mousedown: 2,
mousemove: 2,
mouseup: 2, MSPointerDown: 3,
MSPointerMove: 3,
MSPointerUp: 3
}); me.extend(me.ease = {}, {
quadratic: {
style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
fn: function (k) {
return k * ( 2 - k );
}
},
circular: {
style: 'cubic-bezier(0.1, 0.57, 0.1, 1)', // Not properly "circular" but this looks better, it should be (0.075, 0.82, 0.165, 1)
fn: function (k) {
return Math.sqrt( 1 - ( --k * k ) );
}
},
back: {
style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
fn: function (k) {
var b = 4;
return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1;
}
},
bounce: {
style: '',
fn: function (k) {
if ( ( k /= 1 ) < ( 1 / 2.75 ) ) {
return 7.5625 * k * k;
} else if ( k < ( 2 / 2.75 ) ) {
return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75;
} else if ( k < ( 2.5 / 2.75 ) ) {
return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375;
} else {
return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;
}
}
},
elastic: {
style: '',
fn: function (k) {
var f = 0.22,
e = 0.4; if ( k === 0 ) { return 0; }
if ( k == 1 ) { return 1; } return ( e * Math.pow( 2, - 10 * k ) * Math.sin( ( k - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 );
}
}
}); me.tap = function (e, eventName) {
var ev = document.createEvent('Event');
ev.initEvent(eventName, true, true);
ev.pageX = e.pageX;
ev.pageY = e.pageY;
e.target.dispatchEvent(ev);
}; me.click = function (e) {
var target = e.target,
ev; if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA') {
ev = document.createEvent('MouseEvents');
ev.initMouseEvent('click', true, true, e.view, 1,
target.screenX, target.screenY, target.clientX, target.clientY,
e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
0, null); ev._constructed = true;
target.dispatchEvent(ev);
}
}; return me;
})();
兼容性检测
很烦的事情一而再再而三的在浏览器上面出现,比如我们在chrome要定义动画参数得加上一个前缀webkit,然后ff要使用MozT,这个事情很烦,所以iScroll这段代码就在处理这个事情
var _vendor = (function () {
var vendors = ['t', 'webkitT', 'MozT', 'msT', 'OT'],
transform,
i = 0,
l = vendors.length; for ( ; i < l; i++ ) {
transform = vendors[i] + 'ransform';
if ( transform in _elementStyle ) return vendors[i].substr(0, vendors[i].length-1);
} return false;
})(); function _prefixStyle (style) {
if ( _vendor === false ) return false;
if ( _vendor === '' ) return style;
return _vendor + style.charAt(0).toUpperCase() + style.substr(1);
}
这里做了几个操作:
① 动态创建标签样式——_elementStyle
② 检测样式支持度,并且返回需要的前缀
③ 获取验证结果,比如在chrome下变会返回webkit-XXX
当然,这里要加前缀的样式,一般都与CSS3有关,而下面就会遇到是transform
getTime
me.getTime = Date.now || function getTime () { return new Date().getTime(); };
//获取当前时间戳
extend
最最简单的扩展对象的方法
me.extend = function (target, obj) {
for ( var i in obj ) {
target[i] = obj[i];
}
};
addEvent/removeEvent
事件注册相关,我在想,若是使用了zepto这个代码量会减少点么?
me.addEvent = function (el, type, fn, capture) {
el.addEventListener(type, fn, !!capture);
}; me.removeEvent = function (el, type, fn, capture) {
el.removeEventListener(type, fn, !!capture);
};
momentum
这个方法比较重要,用于计算动画参数,会根据这个计算结果而决定动画运动效果,其实我们昨天的demo也用到了类似的东西
me.momentum = function (current, start, time, lowerMargin, wrapperSize) {
var distance = current - start,
speed = Math.abs(distance) / time,
destination,
duration,
deceleration = 0.0006; destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 );
duration = speed / deceleration; if ( destination < lowerMargin ) {
destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin;
distance = Math.abs(destination - current);
duration = distance / speed;
} else if ( destination > 0 ) {
destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0;
distance = Math.abs(current) + destination;
duration = distance / speed;
} return {
destination: Math.round(destination),
duration: duration
};
};
我们来做一点解释,首先看看我们的参数:
① 这个方法应该是在touchend时候使用,第一个参数为当前鼠标的位置
② 第二个参数是touchstart时候记录的坐标位置
③ 第三个参数为时间参数,便是开始触屏到离开时候所用时间(touchstart到touchend)
PS:这里我们其实可以做一个猜测了,我们有一次触屏的时间与距离,自然可以根据动力加速度计算出此次应该运动的时间与距离
④ 第四个参数是干神马的还不太明确,应该是控制边界位置的,这个就决定了我们不能无限制的拖动wrapper
⑤ 第五个参数为容器的高度
然后我们来以此读一读这里的代码:
① 得出此次拉动的距离distance/然后计算出这次拖动的速度(PS:个人觉得这里操作很不错,我没有想到)
② 然后定义了一些其它参数,deceleration这个用于计算速度/距离的参数,然后两个就是要返回的距离以及时间了
PS:我想说他这里的计算最终位置的函数应该是物理里面的一个计算摩擦参数的公式,尼玛是什么我真的不知道了,还有平方来着......
③ 这里还有一个关键点就是distance有可能是负值,这个会决定向上还是向下运动
④ 一般情况这里就结束来了,然后下面if里面一大段计算是用于处理运动轨迹超出时候的距离与速度重新计算(反弹效果)
好了,这个函数比较关键,他主要返回了最后要去到的位置,已经到这个位置的时间,里面具体的实现我们暂时不关系,后面这个必须理一理
检测与初始化
接下来做了一大段能力检测,比如:
① 是否支持CSS3动画相关(transform、transition)
② 是否支持touch事件
然后做了一些简单的初始化操作,这里新增了一个style对象,为他赋予了CSS动画相关的属性
③ 接下来是一些简单的样式操作,这样有个函数需要注意,他可以获取一个元素真正的位置信息
me.offset = function (el) {
var left = -el.offsetLeft,
top = -el.offsetTop; // jshint -W084
while (el = el.offsetParent) {
left -= el.offsetLeft;
top -= el.offsetTop;
}
// jshint +W084 return {
left: left,
top: top
};
};
④ 动画曲线
me.extend(me.ease = {}, {
quadratic: {
style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
fn: function (k) {
return k * ( 2 - k );
}
},
circular: {
style: 'cubic-bezier(0.1, 0.57, 0.1, 1)', // Not properly "circular" but this looks better, it should be (0.075, 0.82, 0.165, 1)
fn: function (k) {
return Math.sqrt( 1 - ( --k * k ) );
}
},
back: {
style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
fn: function (k) {
var b = 4;
return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1;
}
},
bounce: {
style: '',
fn: function (k) {
if ( ( k /= 1 ) < ( 1 / 2.75 ) ) {
return 7.5625 * k * k;
} else if ( k < ( 2 / 2.75 ) ) {
return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75;
} else if ( k < ( 2.5 / 2.75 ) ) {
return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375;
} else {
return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;
}
}
},
elastic: {
style: '',
fn: function (k) {
var f = 0.22,
e = 0.4; if ( k === 0 ) { return 0; }
if ( k == 1 ) { return 1; } return ( e * Math.pow( 2, - 10 * k ) * Math.sin( ( k - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 );
}
}
});
这里定义了动画曲线供我们选取,我一般使用linear......
PS:这个地方的代码,不明觉厉!!!
自定义点击事件
me.tap = function (e, eventName) {
var ev = document.createEvent('Event');
ev.initEvent(eventName, true, true);
ev.pageX = e.pageX;
ev.pageY = e.pageY;
e.target.dispatchEvent(ev);
}; me.click = function (e) {
var target = e.target,
ev; if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA') {
ev = document.createEvent('MouseEvents');
ev.initMouseEvent('click', true, true, e.view, 1,
target.screenX, target.screenY, target.clientX, target.clientY,
e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
0, null); ev._constructed = true;
target.dispatchEvent(ev);
}
};
iScroll这里干了一件坏事,自己定义了tap以及click,意思是他可以触发绑定到dom上的tap或者click事件
然后整个util便结束了,其中momentum方法很是关键,接下来我们跟着程序流程走了
构造函数
构造函数是iScroll的入口,我们来详细读一读:
wrapper/scroller
为我们的外层结构,再里面一点就是拖动元素了,iscroll的处理是认为wrapper下第一个元素就是可拖动元素,我这里任务不妥......
this.scroller = this.wrapper.children[0];
我们拖动的就是这个scroller了,我为什么说这样不妥呢?因为我如果现在又一个弹出层想使用iScroll的话,若是我弹出层有了wrapper了,我想自己往里面装DOM
而我的DOM搞不好有几个兄弟节点,这个时候我肯定不想自己再包裹一层的,所以iScroll这里的scroller我觉得系统构建比较合理(当然这只是个人认为)
下面还缓存了下当前scroll元素的style
this.scrollerStyle = this.scroller.style;
初始化参数
iScroll当然自己会初始化一些默认属性了:
this.options = { resizeIndicator: true, mouseWheelSpeed: 20, snapThreshold: 0.334, // INSERT POINT: OPTIONS startX: 0,
startY: 0,
scrollY: true,
directionLockThreshold: 5,
momentum: true, bounce: true,
bounceTime: 600,
bounceEasing: '', preventDefault: true,
preventDefaultException: { tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/ }, HWCompositing: true,
useTransition: true,
useTransform: true
};
若是我们传了相关属性会被复写的(这里上面定义了extend,却没有使用):
for ( var i in options ) {
this.options[i] = options[i];
}
能力检测
然后下面一大片结果基本做了一些能力检测的功能,然后将检测结果保存,因为我暂时只关心纵向滑动,所以一些地方便不予关心了
this.x = 0;
this.y = 0;
this.directionX = 0;
this.directionY = 0;
this._events = {};
这一坨东西还是要关注的,方向和初始值
初始化_init
上面一些默认属性定义结束便进入真正的初始化阶段,
_init: function () {
this._initEvents(); if ( this.options.scrollbars || this.options.indicators ) {
this._initIndicators();
} if ( this.options.mouseWheel ) {
this._initWheel();
} if ( this.options.snap ) {
this._initSnap();
} if ( this.options.keyBindings ) {
this._initKeys();
}
// INSERT POINT: _init
},
代码很清晰,我现在的需求关注_initEvents与_initIndicators就好了,其它暂时可以不管,关键点便是事件绑定了
_initEvents
_initEvents: function (remove) {
var eventType = remove ? utils.removeEvent : utils.addEvent,
target = this.options.bindToWrapper ? this.wrapper : window; eventType(window, 'orientationchange', this);
eventType(window, 'resize', this); if ( this.options.click ) {
eventType(this.wrapper, 'click', this, true);
} if ( !this.options.disableMouse ) {
eventType(this.wrapper, 'mousedown', this);
eventType(target, 'mousemove', this);
eventType(target, 'mousecancel', this);
eventType(target, 'mouseup', this);
} if ( utils.hasPointer && !this.options.disablePointer ) {
eventType(this.wrapper, 'MSPointerDown', this);
eventType(target, 'MSPointerMove', this);
eventType(target, 'MSPointerCancel', this);
eventType(target, 'MSPointerUp', this);
} if ( utils.hasTouch && !this.options.disableTouch ) {
eventType(this.wrapper, 'touchstart', this);
eventType(target, 'touchmove', this);
eventType(target, 'touchcancel', this);
eventType(target, 'touchend', this);
} eventType(this.scroller, 'transitionend', this);
eventType(this.scroller, 'webkitTransitionEnd', this);
eventType(this.scroller, 'oTransitionEnd', this);
eventType(this.scroller, 'MSTransitionEnd', this);
},
这段代码,是整个iScroll的核心,整个入口函数其实在这里,我们暂时的关注点又在这里:
if ( utils.hasTouch && !this.options.disableTouch ) {
eventType(this.wrapper, 'touchstart', this);
eventType(target, 'touchmove', this);
eventType(target, 'touchcancel', this);
eventType(target, 'touchend', this);
}
PS:这里有一点让我比较疑惑的就是这里传递进去的fn是一个对象,而不是函数,看来我事件机制一块仍然不到家:
然后进入我们的touch事件,反正现在touchstart便会进入我们的start回调函数
touchStart
_start: function (e) {
// React to left mouse button only
if ( utils.eventType[e.type] != 1 ) {
if ( e.button !== 0 ) {
return;
}
} if ( !this.enabled || (this.initiated && utils.eventType[e.type] !== this.initiated) ) {
return;
} if ( this.options.preventDefault && !utils.isBadAndroid && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) {
e.preventDefault();
} var point = e.touches ? e.touches[0] : e,
pos; this.initiated = utils.eventType[e.type];
this.moved = false;
this.distX = 0;
this.distY = 0;
this.directionX = 0;
this.directionY = 0;
this.directionLocked = 0; this._transitionTime(); this.startTime = utils.getTime(); if ( this.options.useTransition && this.isInTransition ) {
this.isInTransition = false;
pos = this.getComputedPosition();
this._translate(Math.round(pos.x), Math.round(pos.y));
this._execEvent('scrollEnd');
} else if ( !this.options.useTransition && this.isAnimating ) {
this.isAnimating = false;
this._execEvent('scrollEnd');
} this.startX = this.x;
this.startY = this.y;
this.absStartX = this.x;
this.absStartY = this.y;
this.pointX = point.pageX;
this.pointY = point.pageY; this._execEvent('beforeScrollStart');
}
前面做了一系列的兼容性处理,然后记录了一些数据便结束了
结语
今天有点晚了,我也暂时结束了,明天还要上班呢,下次详细研究下touch事件的几个阶段干的事情以及滚动条的实现
【iScroll源码学习01】准备阶段的更多相关文章
- 【iScroll源码学习01】准备阶段 - 叶小钗
[iScroll源码学习01]准备阶段 - 叶小钗 时间 2013-12-29 18:41:00 博客园-原创精华区 原文 http://www.cnblogs.com/yexiaochai/p/3 ...
- 【iScroll源码学习02】分解iScroll三个核心事件点
前言 最近两天看到很多的总结性发言,我想想今年好像我的变化挺大的,是不是该晚上来水一发呢?嗯,决定了,晚上来水一发! 上周六,我们简单模拟了下iScroll的实现,周日我们开始了学习iScroll的源 ...
- 【iScroll源码学习04】分离IScroll核心
前言 最近几天我们前前后后基本将iScroll源码学的七七八八了,文章中未涉及的各位就要自己去看了 1. [iScroll源码学习03]iScroll事件机制与滚动条的实现 2. [iScroll源码 ...
- 【iScroll源码学习03】iScroll事件机制与滚动条的实现
前言 想不到又到周末了,周末的时间要抓紧学习才行,前几天我们学习了iScroll几点基础知识: 1. [iScroll源码学习02]分解iScroll三个核心事件点 2. [iScroll源码学习01 ...
- 【iScroll源码学习00】模拟iScroll
前言 相信对移动端有了解的朋友对iScroll这个库非常熟悉吧,今天我们就来说下我们移动页面的iScroll化 iScroll是我们必学框架之一,我们这次先根据iScroll功能自己实现其功能,然后再 ...
- iscroll源码学习(1)
iscroll是移端端开发的两大利器之一(另一个是fastclick),为了将它整合的avalon,需要对它认真学习一番.下面是我的笔记. 第一天看的是它的工具类util.js //用于做函数节流 v ...
- 【requireJS源码学习01】了解整个requireJS的结构
前言 现在工作中基本离不开requireJS这种模块管理工具了,之前一直在用,但是对其原理不甚熟悉,整两天我们来试着学习其源码,而后在探寻其背后的AMD思想吧 于是今天的目标是熟悉requireJS整 ...
- VUE 源码学习01 源码入口
VUE[version:2.4.1] Vue项目做了不少,最近在学习设计模式与Vue源码,记录一下自己的脚印!共勉!注:此处源码学习方式为先了解其大模块,从宏观再去到微观学习,以免一开始就研究细节然后 ...
- jQuery 源码学习 - 01 - 简洁的 $('...')
首先贴上学习参考资料:[深入浅出jQuery]源码浅析--整体架构,备用地址:chokcoco/jQuery-. jQuery 库,js 开发的一个里程碑,它的出现,让网页开发者们告别荒蛮的上古时代, ...
随机推荐
- Android点击效果
我们在开发网站时,会发现当我们添加<a/>标签后,标签有一个点击效果,比如颜色变化,这样开看起来用户体验会很棒,那么在我们的Android开发中如何加入这样的效果呢?本篇就为大家揭开它的神 ...
- Strophe.js连接XMPP服务器Openfire、Tigase实现Web私聊、群聊(MUC)
XMPP(Extensible Messaging and Presence Protocol)是一种网络即时通讯协议,它基于XML,具有很强的扩展性,被广泛使用在即时通讯软件.网络游戏聊天.Web聊 ...
- 【总结】探索Newlife组件:服务代理利器XAgent的前世今生
本博客所有文章分类的总目录:http://www.cnblogs.com/asxinyu/p/4288836.html Newlife XCode组件相关文章目录:http://www.cn ...
- 转载-centos网络配置(手动设置,自动获取)的2种方法
转载地址:http://blog.51yip.com/linux/1120.html 重新启动网络配置 # service network restart 或 # /etc/init.d/networ ...
- PHP面试出场率较高的题目<转载>
--------------------PHP部分--------------------- PHP中几个输出函数echo,print(),print_r(),sprintf(),var_dump() ...
- 【原创】利用typeface实现不同字体的调用显示及String转换为Unicode
最近工作用到,就写个小demo demo实现从assets中利用typeface调用不同字体,并在editText中显示出来 1.layout中创建activity_main.xml文件 布局代码如下 ...
- Azure操作手册集合
<Windows Azure Platform 系列文章目录> 第一篇: Azure EA Portal管理手册,主要面向Azure企业管理员,介绍- 如何将测试订阅转为 ...
- 自制简单的.Net ORM框架 (一) 简介
在自己研究ORM之前,也使用过几个成熟的ORM方案,例如:EntityFramework,PetaPoco,Dapper 等,用是很好用,但是对自己来说总是不那么方便,EF比较笨重,Dapper要自定 ...
- 解决MVC EF Code First错误:Model compatibility cannot be checked because the EdmMetadata type was not included in the model.
Model compatibility cannot be checked because the EdmMetadata type was not included in the model. En ...
- 数论 - 筛法暴力打表 --- hdu : 12876 Quite Good Numbers
Quite Good Numbers Time Limit: 1000ms, Special Time Limit:2500ms, Memory Limit:65536KB Total submit ...