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,你们有兴趣能够去看看
参考文献