资讯专栏INFORMATION COLUMN

AlloyTouch手势库学习笔记

OBKoro1 / 2583人阅读

摘要:动画库学习笔记可以很方便的用做下拉刷新,抽奖转盘等效果,我一直很好奇他是如何工作的,尤其是它能完美模拟原生的平滑滚动和惯性回弹等效果,而且体积小,速度快。当轴逐渐增加到达时,当前值轴会到达目标值。类似的还有,属性值的排序会造成影响。

AlloyTouch动画库学习笔记

alloyTouch可以很方便的用做下拉刷新,抽奖转盘等效果,我一直很好奇他是如何工作的,尤其是它能完美模拟原生的平滑滚动和惯性回弹等效果,而且体积小,速度快。

阅读代码前,我的思考

拖拽的惯性效果实现,看上去这种效果的原理很简单,但是真正实践的时候还是有疑问:

怎么检测到松开鼠标那一刻的速度(初速)呢?

假设我拖拽的中途突然停止,再松开,要怎么处理?

拖拽力度很大的情况如何处理?

如果惯性滚动的移动的距离超出边界,回弹效果怎么做?

拖拽超出边界的橡皮筋效果怎么做?

源代码阅读

带着这些疑问,我开始了代码阅读之旅,下面的笔记没有完完全全的讲解整个框架,只是挑了我觉得(辣鸡如我)容易疑惑的地方。

初始化
this.isTouchStart = false; 

这个变量是为了判断用户是否是从目标DOM触摸开始,有可能是先在wrapper触摸,再移动至目标DOM,如果是这种情况,不触发滚动。

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

这里有段代码一直让我疑惑很久,为什么touchstart和touchmove间隔大于300ms时,startTimestart(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

touchend

对应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,大家有兴趣可以去看看

参考文献

JavaScript Tween算法及缓动效果

缓动函数速查表

alloyTouch

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88074.html

相关文章

  • 移动Web触摸与运动解决方案AlloyTouch开源啦

    摘要:不论实在应用游戏操作系统等许多层面,监听用户触摸,给用户真实的运动反馈是很常见的应用场景。正是为了解决这类问题而生。版本不支持该事件运动结束比如上面是运动的属性,必须要拥有属性才能正常工作。 传送门 Github地址:https://github.com/AlloyTeam/... 简介 AlloyTouch的本质是运动一个数字,把数字的物理变化映射到你想映射的任何属性上。所以带来了广...

    13651657101 评论0 收藏0
  • 案例 - 收藏集 - 掘金

    摘要:同行这么做使用实现圆形进度条前端掘金在开发微信小程序的时候,遇到圆形进度条的需求。实现也谈数组去重前端掘金的数组去重是一个老生常谈的话题了。百度前端技术学院自定义前端掘金一标签概念元素表示用户界面中项目的标题。 闲话图片上传 - 掘金作者:孙辉,美团金融前端团队成员。15年毕业加入美团,相信技术,更相信技术只是大千世界里知识的一种,个人博客: https://sunyuhui.com ...

    张金宝 评论0 收藏0
  • 案例 - 收藏集 - 掘金

    摘要:同行这么做使用实现圆形进度条前端掘金在开发微信小程序的时候,遇到圆形进度条的需求。实现也谈数组去重前端掘金的数组去重是一个老生常谈的话题了。百度前端技术学院自定义前端掘金一标签概念元素表示用户界面中项目的标题。 闲话图片上传 - 掘金作者:孙辉,美团金融前端团队成员。15年毕业加入美团,相信技术,更相信技术只是大千世界里知识的一种,个人博客: https://sunyuhui.com ...

    huangjinnan 评论0 收藏0
  • Flutter-可以缩放拖拽的图片,小白以及计算机类学生的福音

    摘要:是整个图片在屏幕上的区域,图片显示区域会根据的不同而所不同,通过的方式,计算出最终显示区域。到达边界滚动上下一个图片有了之前缩放拖拽的基础,这部分就比较简单了。 GestureConfig 参数说明参数描述默认值minScale缩放最小值0.8animationMinScale缩放动画最小值,当缩放结束时回到m...

    不知名网友 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<