博客已经停滞了一段时间了,目前一年是在跟js打交道,从最初的android到React-Native然后再到H5,也就短短的1年的时间,几乎明天都在学习,我tm都佩服我自己是怎么坚持下来的……^^,因为有android基础,所以RN上手相对容易一点,但是对于h5真的是从几乎0开始,一开始做东西的时候是灰常恐惧的,都不知道自己写出来的东西运行起来到底咋样,慢慢的遇到的坑多了,也就没什么感觉了(虽然现在依然很菜 ^^),哈哈,就当是心里安慰一下自己了,毕竟接下来的路还很长很长……
说了那么多p话,进入今天的主题哈,虽然iscroll已经出来了很久了,也已经在很多前端大牛身边耳濡目染了,所以对于我(一个刚准备入坑h5的人)来说,iscroll还是带着一层神秘面纱的,一直想着哪天能够好好地去读读她,刚好最近在写列表需求,发现自带的overflow:scroll在原生(ios/android)上运行的时候遇到很多坑,比如ios跟android的滚动效果不一样、内存消耗太大、适配太烂、扩展性太差等等.
先附上iscroll的文档跟github地址:
官方文档:http://iscrolljs.com/#scrollbars
github地址:https://github.com/cubiq/iscroll/
其实用法啥的官方文档已经写的很清楚了,我这边就不啰嗦了,小伙伴自己去看哈(虽然tm都是英文^_^,也还是要去看看的).
之前搞android的养成了一个毛病,就是喜欢边看源码然后结合文档去读别人的第三方库,你还别说,还是有点效果的!所以也建议小伙伴可以这样去读第三方库哈,没必须弄懂每一行代码,大概思路就可以了~
我们直接去github上面把源码clone下来:
可以看到,东西还是挺多的,包括iscroll的源码,然后各种demo,不得不说,作者平时还是比较闲的,哈哈哈~~~~
我们先看看最简单的用法:
<!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: simple</title>
<script type="text/javascript" src="../../build/iscroll.js"></script>
<script type="text/javascript" src="../demoUtils.js"></script>
<script type="text/javascript">
var myScroll;
function loaded () {
myScroll = new IScroll('#wrapper', { mouseWheel: true,scrollbars: true,shrinkScrollbars: 'scale',snap: true});
}
document.addEventListener('touchmove', function (e) { e.preventDefault(); }, isPassive() ? {
capture: false,
passive: false
} : 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: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
-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: 10px;
-webkit-text-size-adjust: none;
-moz-text-size-adjust: none;
-ms-text-size-adjust: none;
-o-text-size-adjust: none;
text-size-adjust: none;
}
</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>
</ul>
</div>
</div>
<div id="footer"></div>
</body>
</html>
用法很简单:
第一步:指定滑动的包裹元素,设置其高度、并且设置其overflow: hidden去除元素默认的滚动
#wrapper {
position: absolute;
z-index: 1;
top: 45px;
bottom: 48px;
left: 0;
width: 100%;
background: #ccc;
overflow: hidden;
}
<div id="wrapper">....</div>
第二步:设置内部滑动元素,也就是滑动的主体部分及其内容
<div id="wrapper">
<div id="scroller">
<ul>
<li>Pretty row 1</li>
<li>Pretty row 2</li>
....
<li>Pretty row 50</li>
</ul>
</div>
</div>
第三步:等dom加载完毕的时候创建iscroll对象,并绑定滑动包裹元素
var myScroll;
function loaded () {
myScroll = new IScroll('#wrapper');
}
好啦~ 简单的几步操作之后,我们就可以开心的玩起来了.
当我们创建一个iscroll对象的时候,我们去源码里看看它干了什么.
首先看iscroll的构造方法:
function IScroll (el, options) {
this.wrapper = typeof el == 'string' ? document.querySelector(el) : el;
this.scroller = this.wrapper.children[0];
this.scrollerStyle = this.scroller.style; // cache style for better performance
this.options = {
resizeScrollbars: true,
mouseWheelSpeed: 20,
snapThreshold: 0.334,
// INSERT POINT: OPTIONS
disablePointer : !utils.hasPointer,
disableTouch : utils.hasPointer || !utils.hasTouch,
disableMouse : utils.hasPointer || utils.hasTouch,
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,
bindToWrapper: typeof window.onmousedown === "undefined"
};
for ( var i in options ) {
this.options[i] = options[i];
}
// Normalize options
this.translateZ = this.options.HWCompositing && utils.hasPerspective ? ' translateZ(0)' : '';
this.options.useTransition = utils.hasTransition && this.options.useTransition;
this.options.useTransform = utils.hasTransform && this.options.useTransform;
this.options.eventPassthrough = this.options.eventPassthrough === true ? 'vertical' : this.options.eventPassthrough;
this.options.preventDefault = !this.options.eventPassthrough && this.options.preventDefault;
// If you want eventPassthrough I have to lock one of the axes
this.options.scrollY = this.options.eventPassthrough == 'vertical' ? false : this.options.scrollY;
this.options.scrollX = this.options.eventPassthrough == 'horizontal' ? false : this.options.scrollX;
// With eventPassthrough we also need lockDirection mechanism
this.options.freeScroll = this.options.freeScroll && !this.options.eventPassthrough;
this.options.directionLockThreshold = this.options.eventPassthrough ? 0 : this.options.directionLockThreshold;
this.options.bounceEasing = typeof this.options.bounceEasing == 'string' ? utils.ease[this.options.bounceEasing] || utils.ease.circular : this.options.bounceEasing;
this.options.resizePolling = this.options.resizePolling === undefined ? 60 : this.options.resizePolling;
if ( this.options.tap === true ) {
this.options.tap = 'tap';
}
// https://github.com/cubiq/iscroll/issues/1029
if (!this.options.useTransition && !this.options.useTransform) {
if(!(/relative|absolute/i).test(this.scrollerStyle.position)) {
this.scrollerStyle.position = "relative";
}
}
if ( this.options.shrinkScrollbars == 'scale' ) {
this.options.useTransition = false;
}
this.options.invertWheelDirection = this.options.invertWheelDirection ? -1 : 1;
// INSERT POINT: NORMALIZATION
// Some defaults
this.x = 0;
this.y = 0;
this.directionX = 0;
this.directionY = 0;
this._events = {};
// INSERT POINT: DEFAULTS
this._init();
this.refresh();
this.scrollTo(this.options.startX, this.options.startY);
this.enable();
}
我们慢慢来看哈,首先是获取wrapper跟scroller元素:
this.wrapper = typeof el == 'string' ? document.querySelector(el) : el;
this.scroller = this.wrapper.children[0];
然后就是初始化一些参数:
this.options = {
resizeScrollbars: true, //是否重置scrollbar的大小
mouseWheelSpeed: 20, //鼠标滑动的速度
snapThreshold: 0.334, //翻页的距离值
// INSERT POINT: OPTIONS
disablePointer : !utils.hasPointer,
disableTouch : utils.hasPointer || !utils.hasTouch,
disableMouse : utils.hasPointer || utils.hasTouch,
startX: 0, //横向滑动的的初始值
startY: 0,//竖直滑动的初始值
scrollY: true, //是否运行竖直滑动
...
};
当然还有一些包括指示器的一些参数(自定义、样式等等),文档上貌似都没列出来,小伙伴只能去看源码了.
然后就是比较重要的几个初始化了:
this._init();//初始化()
this.refresh(); //获取一些基本参数(如果scroller的高度变化了需要调用此方法)
this.scrollTo(this.options.startX, this.options.startY);//调整初始位置
this.enable();//默认允许滑动
_init: function () {
this._initEvents();//绑定事件(鼠标滑动、touch等等)
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
},
那么既然iscroll禁用了默认的滑动,那么它是怎么滑动的呢?小伙伴猜也应该猜到了,就是监听元素(默认wrapper)的鼠标滑动、touch等等事件然后做出相应计算:
_initEvents: function (remove) {
var eventType = remove ? utils.removeEvent : utils.addEvent,
target = this.options.bindToWrapper ? this.wrapper : window;
eventType(window, 'orientationchange', this);//当转换屏幕的时候refresh重置参数
eventType(window, 'resize', this);//当window的size变换的时候时候refresh重置参数
if ( this.options.click ) {//wrapper的点击事件
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 ) {//处理指针事件,主要处理tap事件
eventType(this.wrapper, utils.prefixPointerEvent('pointerdown'), this);
eventType(target, utils.prefixPointerEvent('pointermove'), this);
eventType(target, utils.prefixPointerEvent('pointercancel'), this);
eventType(target, utils.prefixPointerEvent('pointerup'), this);
}
if ( utils.hasTouch && !this.options.disableTouch ) {//监听touch事件
eventType(this.wrapper, 'touchstart', this);
eventType(target, 'touchmove', this);
eventType(target, 'touchcancel', this);
eventType(target, 'touchend', this);
}
eventType(this.scroller, 'transitionend', this);//监听transition过渡动画过程
eventType(this.scroller, 'webkitTransitionEnd', this);
eventType(this.scroller, 'oTransitionEnd', this);
eventType(this.scroller, 'MSTransitionEnd', this);
},
我们重点看一下,当我们的手指滑动的时候,scroller元素是怎么滑动的:
handleEvent: function (e) {
switch ( e.type ) {
...
case 'touchmove':
case 'pointermove':
case 'MSPointerMove':
case 'mousemove':
this._move(e);
break;
...
}
}
处理滑动事件:
_move: function (e) {
if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) {
return;
}
if ( this.options.preventDefault ) { // increases performance on Android? TODO: check!
e.preventDefault();
}
var point = e.touches ? e.touches[0] : e,
deltaX = point.pageX - this.pointX,
deltaY = point.pageY - this.pointY,
timestamp = utils.getTime(),
newX, newY,
absDistX, absDistY;
this.pointX = point.pageX;
this.pointY = point.pageY;
this.distX += deltaX;
this.distY += deltaY;
absDistX = Math.abs(this.distX);
absDistY = Math.abs(this.distY);
// We need to move at least 10 pixels for the scrolling to initiate
if ( timestamp - this.endTime > 300 && (absDistX < 10 && absDistY < 10) ) {
return;
}
// If you are scrolling in one direction lock the other
if ( !this.directionLocked && !this.options.freeScroll ) {
if ( absDistX > absDistY + this.options.directionLockThreshold ) {
this.directionLocked = 'h'; // lock horizontally
} else if ( absDistY >= absDistX + this.options.directionLockThreshold ) {
this.directionLocked = 'v'; // lock vertically
} else {
this.directionLocked = 'n'; // no lock
}
}
if ( this.directionLocked == 'h' ) {
if ( this.options.eventPassthrough == 'vertical' ) {
e.preventDefault();
} else if ( this.options.eventPassthrough == 'horizontal' ) {
this.initiated = false;
return;
}
deltaY = 0;
} else if ( this.directionLocked == 'v' ) {
if ( this.options.eventPassthrough == 'horizontal' ) {
e.preventDefault();
} else if ( this.options.eventPassthrough == 'vertical' ) {
this.initiated = false;
return;
}
deltaX = 0;
}
deltaX = this.hasHorizontalScroll ? deltaX : 0;
deltaY = this.hasVerticalScroll ? deltaY : 0;
newX = this.x + deltaX;
newY = this.y + deltaY;
// Slow down if outside of the boundaries
if ( newX > 0 || newX < this.maxScrollX ) {
newX = this.options.bounce ? this.x + deltaX / 3 : newX > 0 ? 0 : this.maxScrollX;
}
if ( newY > 0 || newY < this.maxScrollY ) {
newY = this.options.bounce ? this.y + deltaY / 3 : newY > 0 ? 0 : this.maxScrollY;
}
this.directionX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0;
this.directionY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0;
if ( !this.moved ) {
this._execEvent('scrollStart');
}
this.moved = true;
this._translate(newX, newY);
/* REPLACE START: _move */
if ( timestamp - this.startTime > 300 ) {
this.startTime = timestamp;
this.startX = this.x;
this.startY = this.y;
}
/* REPLACE END: _move */
},
代码有点多,小伙伴别被吓住了哈,主要就是计算上一次与这一次滑动的距离,然后设置元素的偏移量,我们可以看到最后有一行代码:
this._translate(newX, newY);
_translate: function (x, y) {
if ( this.options.useTransform ) {
/* REPLACE START: _translate */
this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ;
/* REPLACE END: _translate */
} else {
x = Math.round(x);
y = Math.round(y);
this.scrollerStyle.left = x + 'px';
this.scrollerStyle.top = y + 'px';
}
this.x = x;
this.y = y;
if ( this.indicators ) {
for ( var i = this.indicators.length; i--; ) {
this.indicators[i].updatePosition();
}
}
// INSERT POINT: _translate
},
可以看到,最后也就是设置x轴或者y轴的偏移量:
x = Math.round(x);
y = Math.round(y);
this.scrollerStyle.left = x + 'px';
this.scrollerStyle.top = y + 'px';
原理其实很简单,但是iscroll可不单单如此简单哈~~ 她可不简单,慢慢研究吧!! 睡啦睡啦~~生命重要!!!小伙伴下期见哈,大牛勿喷!!