背景
近來開闢挪動端項目用到了iscroll,踩了不少坑,因而瀏覽源碼加深下對這個東西的相識。
本次瀏覽的是iscroll-lite,包含了重要的功用,比完整版少了鼠標滾輪,轉動后對齊到特定位置,按鍵綁定及定製轉動條等功用。
主體組織
以當前的5.2版本為例,前三百行是東西函數,封裝了一些東西。三百行到四百行是組織函數IScroll,起到設置和初始化的結果。四百行到一千修正了IScroll的原型,給IScroll實例加上了種種要領。末了是模塊化相干的推斷。整體的組織是很清楚的。
東西函數
_prefixStyle
用於瀏覽器兼容。
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.extend(me, {
hasTransform: _transform !== false,
hasPerspective: _prefixStyle('perspective') in _elementStyle,
hasTouch: 'ontouchstart' in window,
hasPointer: !!(window.PointerEvent || window.MSPointerEvent), // IE10 is prefixed
hasTransition: _prefixStyle('transition') in _elementStyle
});
另有一些事宜監聽方面的處置懲罰函數,處置懲罰class的函數,處置懲罰慣性的函數momentum
父元素的位置
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.eventType = {}, {
touchstart: 1,
touchmove: 1,
touchend: 1,
...略...
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 );
}
},
....略....
});
tap和click的事宜模仿
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 ( !(/(SELECT|INPUT|TEXTAREA)/i).test(target.tagName) ) {
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/initMouseEvent
// initMouseEvent is deprecated.
ev = document.createEvent(window.MouseEvent ? 'MouseEvents' : 'Event');
ev.initEvent('click', true, true);
...略...
target.dispatchEvent(ev);
}
};
touchAction的設定
me.getTouchAction = function(eventPassthrough, addPinch) {
var touchAction = 'none';
if ( eventPassthrough === 'vertical' ) {
touchAction = 'pan-y';
} else if (eventPassthrough === 'horizontal' ) {
touchAction = 'pan-x';
}
if (addPinch && touchAction != 'none') {
// add pinch-zoom support if the browser supports it, but if not (eg. Chrome <55) do nothing
touchAction += ' pinch-zoom';
}
return touchAction;
};
元素的位置
me.getRect = function(el) {
if (el instanceof SVGElement) {
var rect = el.getBoundingClientRect();
return {
top : rect.top,
left : rect.left,
width : rect.width,
height : rect.height
};
} else {
return {
top : el.offsetTop,
left : el.offsetLeft,
width : el.offsetWidth,
height : el.offsetHeight
};
}
};
組織函數
先是獵取了wrapper和scroller,接着是一些才能檢測。
在這裏經由過程eventPassthrough,scrollY,scrollX,freeScroll來設置轉動的方向。只要scrollY是默以為true,其他都是未設定undefined.
當eventPassthrough的值為vertical時,在垂直方向不運用iscroll的轉動,運用原生的轉動。為horizontal時,則程度方向云云。想要二維自在轉動,不能設置該值。
this.options.eventPassthrough = this.options.eventPassthrough === true ? 'vertical' : this.options.eventPassthrough;
this.options.scrollY = this.options.eventPassthrough == 'vertical' ? false : this.options.scrollY;
this.options.scrollX = this.options.eventPassthrough == 'horizontal' ? false : this.options.scrollX;
this.options.freeScroll = this.options.freeScroll && !this.options.eventPassthrough;
接着另有其他屬性的設定,末了挪用_init,refresh,scrollTo,enable等函數
一些真相上綁定的函數
_init
只挪用了_initEvents函數
_initEvents
在這裏依據設置給wrapper或是window加入了鼠標事宜,pointer事宜和觸摸事宜,transition事宜等事宜的監聽。這裏用到了一種少見的事宜綁定要領,在這裏事宜綁定的第三個參數不是罕見的function,而是iscroll對象。而iscroll對象里有一個要領handleEvent,這類體式格局的優點就是能夠通報this
handleEvent: function (e) {
switch ( e.type ) {
case 'touchstart':
case 'pointerdown':
case 'MSPointerDown':
case 'mousedown':
this._start(e);
break;
case 'touchmove':
case 'pointermove':
case 'MSPointerMove':
case 'mousemove':
this._move(e);
break;
case 'touchend':
case 'pointerup':
case 'MSPointerUp':
case 'mouseup':
case 'touchcancel':
case 'pointercancel':
case 'MSPointerCancel':
case 'mousecancel':
this._end(e);
...略...
}
}
refresh
保留wrapper和scroller的尺寸信息,及wrapper的位置信息。設定最大轉動間隔,為負數。
接着推斷是不是存在程度轉動或是垂直轉動
this.maxScrollX = this.wrapperWidth - this.scrollerWidth;
this.hasHorizontalScroll = this.options.scrollX && this.maxScrollX < 0;
假如瀏覽器支撐pointer,運用touchAction來限定wrapper。
末了挪用resetPosition函數來推斷是不是須要重置位置
_start
保留事宜的範例,假如跟已保留的事宜範例不是統一類,不繼承實行函數。
假如scroller在轉動狀況,挪用getComputedPosition函數來獵取scroller的位置,挪用_translate要領讓scroller停在當前位置。
保留當前的位置信息和觸發事宜的位置信息,設置狀況信息
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.startX = this.x;
this.startY = this.y;
this.absStartX = this.x;
this.absStartY = this.y;
this.pointX = point.pageX;
this.pointY = point.pageY;
_move
獵取事宜的位置信息,對照_start中保留的位置信息,盤算偏移值
var point = e.touches ? e.touches[0] : e,
deltaX = point.pageX - this.pointX,
deltaY = point.pageY - this.pointY,
保留新的位置信息,位移信息
this.pointX = point.pageX;
this.pointY = point.pageY;
this.distX += deltaX;
this.distY += deltaY;
假如偏移小於10像素不實行轉動。偏移的大小值和設定來依據推斷轉動的方向。末了挪用_translate函數來轉動
_translate
依據設定,修正轉動條中的transform的值或是修正left和top的值來舉行詳細的轉動,保留信息到this.x和this.y
if ( this.options.useTransform ) {
this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ;
} else {
x = Math.round(x);
y = Math.round(y);
this.scrollerStyle.left = x + 'px';
this.scrollerStyle.top = y + 'px';
}
_end
讓scroller轉動到整數位置,依據位移和觸發事宜的時候的狀況,摸你tap事宜、click事宜或是實行flick事宜或挪用scrollTO函數舉行慣性轉動。
scrollTo
重如果挑選動畫結果,挪用__translate或是_animate來舉行轉動
_animate
依據所選的動畫結果,在每個requestAnimationFrame下挪用_translate函數