alloyTouch能夠很方便的用作下拉刷新,抽獎轉盤等效果,我一直很好奇他是如何工做的,尤爲是它能完美模擬原生的平滑滾動和慣性回彈等效果,並且體積小,速度快。javascript
拖拽的慣性效果實現,看上去這種效果的原理很簡單,可是真正實踐的時候仍是有疑問:html
怎麼檢測到鬆開鼠標那一刻的速度(初速)呢?java
假設我拖拽的中途忽然中止,再鬆開,要怎麼處理?git
拖拽力度很大的狀況如何處理?github
若是慣性滾動的移動的距離超出邊界,回彈效果怎麼作?算法
拖拽超出邊界的橡皮筋效果怎麼作?spring
帶着這些疑問,我開始了代碼閱讀之旅,下面的筆記沒有完徹底全的講解整個框架,只是挑了我以爲(辣雞如我)
容易疑惑的地方。瀏覽器
this.isTouchStart = false;
這個變量是爲了判斷用戶是不是從目標DOM觸摸開始,有多是先在wrapper觸摸,再移動至目標DOM,若是是這種狀況,不觸發滾動。app
bind(this.element, "touchstart", this._start.bind(this)); bind(this.eventTarget, "touchend", this._end.bind(this)); bind(this.eventTarget, "touchcancel", this._cancel.bind(this));
接下來重點就在於這3個函數了,初始化綁定DOM的事件。框架
對應AlloyTouch.prototype._start
。
_start: function (evt) { this.isTouchStart = true; this.touchStart.call(this, evt, this.target[this.property]); cancelAnimationFrame(this.tickID); this._calculateIndex(); this.startTime = new Date().getTime(); //起始時間 this.x1 = this.preX = evt.touches[0].pageX; this.y1 = this.preY = evt.touches[0].pageY; this.start = this.vertical ? this.preY : this.preX; //startPoint this._firstTouchMove = true; //這裏纔是判斷是否初次觸摸 this._preventMove = false; },
對應AlloyTouch.prototype._move
。
這裏有段代碼一直讓我疑惑好久,爲何touchstart和touchmove間隔大於300ms時,startTime
和start(startPoint)
要從新設置呢?
按個人理解,爲了方便檢測速度,當這次touchmove事件觸發時間比startTime
大於300ms時,從新設定計算速度的startPoint
,這樣能夠在拖拽軌跡中截取合理的起止長度和時間間隔,計算初速,通常拖拽過程有如下3中狀況:
假設拖拽的時長小於300ms,startPoint
則用touchstart
時設置的,
假設拖拽時長大於300ms,startPoint
用知足條件的新touchmove點。
拖拽中途中止,不產生慣性效果(通常狀況下,鼠標中止的時間會大於300ms)
這裏我有個疑問,爲何不直接用最後一個touchmove點做爲startPoint呢?
個人理解是,最後觸發的touchmove事件和touchend事件間隔時間很短,雖然間隔時間(dt)能夠取得的精度很高,可是,移動的距離差(dv)的單位是px
,假設物體移動了1.999px,最後瀏覽器仍是按1px計算,在dt很小的的狀況下,偏差就變大了。
對於問題5:橡皮筋效果的實現:拖拽時,若是超出邊界,則增長移動的阻力,即用outerFactor
。
對應AlloyTouch.prototype._end
。
var dt = new Date().getTime() - this.startTime; if (dt < 300) {...}
對於問題4:判斷時間間隔是否小於300ms,若是大於,則斷定是拖着不動,再鬆開,此時沒有慣性效果。
對於問題3:慣性滾動的距離destination
超出邊界max
且大於最大值maxRegion
(默認600px)時,則慣性滾動的最大距離爲max + springMaxRegion(默認60px)
,以下圖。
_to
的實現alloyTouch內部全部的動畫執行都交給_to
完成,相似$.fn.animate
,實現以下
/** * 執行過分效果 * @param value 目標值 * @param time 過渡時間 * @param ease 緩動函數 * @param onChange * @param onEnd * @private */ _to: function (value, time, ease, onChange, onEnd) { if (this.fixed) return; var el = this.target, property = this.property; var current = el[property]; var dv = value - current; var beginTime = new Date(); var self = this; var toTick = function () { var dt = new Date() - beginTime; if (dt >= time) { el[property] = value; onChange && onChange.call(self, value); onEnd && onEnd.call(self, value); return; } el[property] = dv * ease(dt / time) + current; el[property] = a; self.tickID = requestAnimationFrame(toTick); //cancelAnimationFrame必須在 tickID = requestAnimationFrame(toTick);的後面 onChange && onChange.call(self, el[property]); }; toTick(); },
咱們替換一下原有的ease函數,也能夠達到一樣效果,這裏我使用TweenJS
提供的緩動函數
let a = Tween.Quad.easeOut(dt, current, dv, time); // console.log(a); // el[property] = dv * ease(dt / time) + current; el[property] = a; self.tickID = requestAnimationFrame(toTick); //cancelAnimationFrame必須在 tickID = requestAnimationFrame(toTick);的後面 onChange && onChange.call(self, el[property]);
alloyTouch的大致思路就是在一段拖拽軌跡上,以touchend做爲endPoint
,再向前300ms內選取一個startPoint
,由兩點計算出初速。
var Tween = { Quad: { /** * * @param t 時間(x軸) * @param b 初始值 * @param c 改變的大小 * @param d 持續時間 * @return {*} */ easeOut: function(t,b,c,d){ return -c *(t/=d)*(t-2) + b; } } }
x軸是時間,y軸是當前值,b是y軸的初始值,x軸的初始值是0,t是當前時間。當t(x軸)逐漸增長到達d時,當前值(y軸)會到達目標值(b+c)。
使用alloyTouch能夠很方便的作出相似IOS的select效果
作3D效果就更方便啦
CSS3中transform:rotateX(30px) translateZ(50px)
和transform: translateZ(50px) rotateX(30px)
的效果是不同的!!!
前者是先旋轉(此時Z軸的方向已經發生改變),再往Z軸偏移50px,後者是先往Z軸偏移50px,並以當前爲點基準,再旋轉。相似的還有perspective
,屬性值的排序會形成影響。
對此,我提了一個issue,你們有興趣能夠去看看
參考文獻