AlloyTouch手勢庫學習筆記

AlloyTouch動畫庫學習筆記

alloyTouch能夠很方便的用作下拉刷新,抽獎轉盤等效果,我一直很好奇他是如何工做的,尤爲是它能完美模擬原生的平滑滾動和慣性回彈等效果,並且體積小,速度快。javascript

閱讀代碼前,個人思考

拖拽的慣性效果實現,看上去這種效果的原理很簡單,可是真正實踐的時候仍是有疑問:html

  1. 怎麼檢測到鬆開鼠標那一刻的速度(初速)呢?java

  2. 假設我拖拽的中途忽然中止,再鬆開,要怎麼處理?git

  3. 拖拽力度很大的狀況如何處理?github

  4. 若是慣性滾動的移動的距離超出邊界,回彈效果怎麼作?算法

  5. 拖拽超出邊界的橡皮筋效果怎麼作?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的事件。框架

touchstart

對應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;
        },

touchmove

對應AlloyTouch.prototype._move

clipboard.png

這裏有段代碼一直讓我疑惑好久,爲何touchstart和touchmove間隔大於300ms時,startTimestart(startPoint)要從新設置呢?
按個人理解,爲了方便檢測速度,當這次touchmove事件觸發時間比startTime大於300ms時,從新設定計算速度的startPoint,這樣能夠在拖拽軌跡中截取合理的起止長度和時間間隔,計算初速,通常拖拽過程有如下3中狀況:

  1. 假設拖拽的時長小於300ms,startPoint則用touchstart時設置的,

  2. 假設拖拽時長大於300ms,startPoint用知足條件的touchmove點。

  3. 拖拽中途中止,不產生慣性效果(通常狀況下,鼠標中止的時間會大於300ms

這裏我有個疑問,爲何不直接用最後一個touchmove點做爲startPoint呢?
個人理解是,最後觸發的touchmove事件和touchend事件間隔時間很短,雖然間隔時間(dt)能夠取得的精度很高,可是,移動的距離差(dv)的單位是px,假設物體移動了1.999px,最後瀏覽器仍是按1px計算,在dt很小的的狀況下,偏差就變大了。

clipboard.png

對於問題5:橡皮筋效果的實現:拖拽時,若是超出邊界,則增長移動的阻力,即用outerFactor

touchend

對應AlloyTouch.prototype._end

var dt = new Date().getTime() - this.startTime;
  if (dt < 300) {...}

對於問題4:判斷時間間隔是否小於300ms,若是大於,則斷定是拖着不動,再鬆開,此時沒有慣性效果。

clipboard.png

對於問題3:慣性滾動的距離destination超出邊界max且大於最大值maxRegion(默認600px)時,則慣性滾動的最大距離爲max + springMaxRegion(默認60px),以下圖。

clipboard.png

_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效果

GIF.gif

作3D效果就更方便啦

clipboard.png

要注意的問題

CSS3中transform:rotateX(30px) translateZ(50px)transform: translateZ(50px) rotateX(30px)的效果是不同的!!!
前者是先旋轉(此時Z軸的方向已經發生改變),再往Z軸偏移50px,後者是先往Z軸偏移50px,並以當前爲點基準,再旋轉。相似的還有perspective,屬性值的排序會形成影響。
對此,我提了一個issue,你們有興趣能夠去看看

參考文獻

  1. JavaScript Tween算法及緩動效果

  2. 緩動函數速查表

  3. alloyTouch

相關文章
相關標籤/搜索