前言
在这个触摸屏时代,人性化的手势操作已经渗透到我们生活的方方面面。 现代应用程序越来越注重与用户的交互和体验。 手势是最直接、最有效的交互方式。 良好的手势交互可以减少用户的成本和流程,大大提升用户体验。
最近公司很多项目对手势的需求很高,而现有的手势库还没有完全覆盖,所以开发了一个轻量级、易用的联通手势库。 本博文主要从后端的角度分析联通侧常用手势的原理,以及学习过程中用到的物理知识。 希望能给大家带来一点启发,也希望前辈们指出不足甚至错误的地方,谢谢。
主要讲解一下项目中经常用到的五个手势:
尖端:
因为很多基础库中都包含了tap和swipe,所以为了方便没有包含它们,但是如果需要的话可以进行扩展;
实现原理
众所周知,所有的手势都是基于浏览器原生的touchstart、touchmove、touchend、touchcancel的底层封装,所以封装的思路就是通过独立事件一一的反弹仓库handleBus,然后满足原生触摸事件中的条件触发并发送估计的参数值,完成手势操作。 实现原理比较简单清晰,不用担心,我们先整理一下用到的一些物理概念,并结合代码,将物理应用到实际问题中。 物理部分可能比较枯燥,但希望你坚持下去,相信你会有所收获。
基础物理知识功能
我们常见的坐标系属于线性空间,或者说向量空间(VectorSpace)。 这个空间是点(Point)和向量(Vector)的集合;
观点
可以理解为我们的坐标点,比如原点O(0,0),A(-1,2),触摸点的坐标可以通过原生storm对象的touches获取,参数index代表接触点;
向量
它是坐标系中既有大小又有方向的线段。 例如,从原点O(0,0)到点A(1,1)的箭头线段称为向量a,则a=(1-0,1 -0)=(1,1);
如右图所示,i和j向量称为坐标系的单位向量,也称为基向量。 我们常用的坐标系单位是1,即i=(1,0); j=(0 ,1);
获取向量的函数:
向量模
表示向量的宽度,记作|a|,是标量,只有大小,没有方向;
几何意义表示以x和y为直角边的直角三角形的底,通过毕达哥拉斯定律估计;
获取长度函数:
向量的点积
向量还具有可计算的属性。 它可以执行加法、减法、乘法、定量乘积和向量乘积等运算。 接下来,我们将介绍我们使用的数值积的概念,也称为点积,它的定义为一个公式:
当a=(x1,y1),b=(x2,y2)时,则a·b=|a|·|b|·cosθ=x1·x2+y1·y2;
共线法则
共线,即两个向量处于平行状态。 当a=(x1,y1),b=(x2,y2)时,存在唯一实数λ,使得a=λb,将坐标点代入后,可得x1·y2=y1·x2;
因此,当x1·y2-x2·y1>0时,斜率ka>kb,所以此时b向量相对a向量顺时针旋转,否则反秒;
旋转角度
通过数积公式,我们可以推导出两个向量的倾斜角度:
cosθ=(x1 x2+y1 y2)/(|a||b|);
那么我们就可以通过共线定律来确定旋转方向。 该函数定义为:
矩阵与变换
因为空间最本质的特征就是能够容纳运动html5模拟器html5模拟器,所以在线性空间中,
我们用向量来描述物体,矩阵用来描述物体的运动;
矩阵如何描述运动?
我们知道,一个向量可以通过坐标系基向量来确定,比如a=(-1,2),而我们通常约定的基向量是i=(1,0)和j=(0,1) ; 所以 :
a=-1i+2j=-1(1,0)+2(0,1)=(-1+0,0+2)=(-1,2);
对于矩阵变换,虽然通过矩阵对基向量进行了变换,但完成了向量的变换;
比如前面的栗子,向量a经过矩阵(1,2,3,0)变换。 此时基向量i由(1,0)变换为(1,-2),j由(0,1)变换为(3,0),按照其中的推论,则
a=-1i+2j=-1(-1,2)+2(3,0)=(5,-2);
如右图所示:
图A表示变换前的坐标系,此时a=(-1,2),矩阵变换后,基向量i,j的变换引起坐标系的变换,得到右图B ,所以 a 向量由(-1,2)变换为(5,-2);
虽然向量和坐标系的关系不变(a=-1i+2j),但引起坐标系变化的是基向量,然后坐标系继承了这种关系,导致向量变化;
组合代码
虽然CSS变换等变换都是通过矩阵来进行的,但我们平时写的translate/rotate等句型,类似于为了快速使用而封装好的句型糖,都会在底层转换成矩阵。 如translate:translate(-30px,-30px)会转换为transform:matrix(1,0,0,1,30,30);
一般来说,在二维坐标系中,只需要一个2X2的矩阵就足以描述所有的变换,但是由于CSS是在3D环境下,所以CSS中使用了3X3的矩阵,表示为:
第三行的0,0,1代表z轴的默认参数。 该矩阵中,(a,b)为坐标轴的i轴,(c,d)为j轴,e为x轴的倾斜量,f为x轴的倾斜量y 轴; 很容易理解,translate不会导致i,j基数改变,而是倾斜的,所以translate(-30px,-30px)==>matrix(1,0,0,1,30,30)~
所有的变换语句都会进行相应的变换,如下:
//发生倾斜,但基向量不变;
变换:平移(x,y)==>变换:矩阵(1,0,0,1,x,y)
//基向量旋转;
变换:旋转(θdeg)==>变换:矩阵(cos(θ·π/180),sin(θ·π/180),-sin(θ·π/180),cos(θ·π/180), 0,0)
//基向量放大,方向不变;
变换:比例(s)==>变换:矩阵(s,0,0,s,0,0)
诸如translate/rotate/scale之类的句子模式非常强大,使得我们的代码更具可读性和方便编写,而matrix具有更强大的变换功能。 通过矩阵,可以发生任何变换,比如我们常见的镜像对称,transform :matrix(-1,0,0,1,0,0);
矩阵转
但是矩阵其实很强大,只是可读性不好,但是我们的写法是通过translate/rotate/scale的属性,但是通过getCompulatedStyle读取到的transform是matrix:
变换:矩阵(1.41421,1.41421,-1.41421,1.41421,-50,-50);
这个元素发生了怎样的变化? 。 这是一脸茫然的样子。 -_-|||
因此,我们必须有一种方法将矩阵转换为更熟悉的平移/旋转/缩放形式。 了解了原理之后我们就可以开始表演啦~
我们知道前4个参数会同时受到旋转和缩放的影响,并且有两个变量,所以我们需要通过前两个参数按照前面的转换方法枚举出两个不同的方程:
cos(θ·π/180)*s=1.41421;
sin(θ·π/180)*s=1.41421;
将两个不等式相除,就可以轻松求出θ和s,完美! ! 函数如下:
手势原理
接下来我们将在实际环境中使用前面的函数,以图的形式模拟手势的操作,并简单解释一下手势估计的原理。 我希望你在理解了这个基本原理之后,能够创造出更多令人眼花缭乱的手势,就像我们在mac触控板上使用的手势一样。
图例如下:
点:代表拇指的触摸点;
两点之间的实线段:表示两指操作时形成的矢量;
a向量/点A:表示touchstart时获取的初始向量/初始点;
b向量/B点:表示touchmove时获取的实时向量/实时点;
轴顶部的公式表示要估计的值;
拖曳(拖曳风暴)
上图模拟了拖动手势,从A点连接到B点。我们要估计的是这个过程中的偏转量;
为此,我们在 touchstart 中记录初始点 A 的坐标:
//获取初始点A;
letstartPoint = getPoint(ev,0);
然后获取touchmove风暴中的当前点并实时估计△x和△y:
// 实时获取初始点B;
letcurPoint=getPoint(ev,0);
//通过A、B两点,实时估计位移增量,触发拖曳风暴并发送参数;
_eventFire('拖动',{
德尔塔:{
deltaX:curPoint.x-startPoint.x,
deltaY:curPoint.y-startPoint.y,
},
来源:ev,
});
Tips:开火函数是遍历执行拖拽风暴对应的反弹仓库;
捏合(两指缩放)
上图是两指缩放的模拟图。 两根手指从向量a放大到向量b。 通过估计初始状态下向量a的模和touchmove中得到的向量b的模,可以得到缩放值:
//估计touchstart中初始两根手指的向量模;
letvector1=getVector(第二点,起始点);
letpinchStartLength=getLength(向量1);
//估计touchmove中的实时双向向量模;
letvector2=getVector(curSecPoint,curPoint);
letpinchLength = getLength(向量2);
this._eventFire('捏',{
德尔塔:{
比例:pinchLength/pinchStartLength,
},
来源:ev,
});
旋转(用两根手指旋转)
最初,双向向量a旋转为向量b,θ就是我们需要的值,所以只要使用我们之前创建的getAngle函数,我们就可以求出旋转的角度:
//一个向量;
letvector1=getVector(第二点,起始点);
//b向量;
letvector2=getVector(curSecPoint,curPoint);
//触发风暴;
this._eventFire('旋转',{
德尔塔:{
旋转:getAngle(向量1,向量2),
},
来源:ev,
});
singlePinch(单指缩放)
与之前的手势不同,单指缩放和单指旋转都需要几个独特的概念:
操作元素(operator):需要操作的元素。 前三个手势似乎并不关心操作元素,因为正确的参数值可以仅通过手势本身来估计,而单指缩放和旋转需要依赖操作元素的参考点(操作元件的中心点)进行估计;
按钮:由于单指手势和拖动手势是互相冲突的,所以需要特殊的交互方式来区分,这里是通过特定区域来区分的,类似于按钮,在对按钮进行操作时,就是用一根手指进行缩放或旋转,而在按钮区域之外,则是常规的拖动。 实践证明,这是一种用户容易接受、体验较好的操作方式;
图中,单指将向量a放大为向量b,操作数的中心(正圆)也被放大,此时的缩放值为向量b的模/向量a的模;
//估计单指操作时的参考点,得到操作者的中心点;
让 singleBasePoint = getBasePoint(operator);
//估计touchstart中的初始向量模;
letpinchV1=getVector(startPoint, singleBasePoint);
singlePinchStartLength=getLength(pinchV1);
//估计touchmove中的实时矢量模式;
inchV2=getVector(curPoint,singleBasePoint);
singlePinchLength=getLength(pinchV2);
//触发风暴;
this._eventFire('singlePinch',{
德尔塔:{
比例:singlePinchLength/singlePinchStartLength,
},
来源:ev,
});
singleRotate(单指旋转)
结合一指缩放和两指旋转,很容易知道θ就是我们需要的旋转角度;
//获取初始向量和实时向量
letrotateV1=getVector(startPoint, singleBasePoint);
letrotateV2=getVector(curPoint,singleBasePoint);
// 通过getAngle获取旋转角度并触发风暴;
this._eventFire('singleRotate',{
德尔塔:{
旋转:getAngle(旋转V1,旋转V2),
},
来源:ev,
});
运动增量
由于touchmove风暴是高频实时触发风暴,虽然一次拖拽操作触发了N次touchmove风暴,但估计值只是一个增量,即代表了一次touchmove风暴的减少值,仅代表了一次touchmove风暴的减少值。一段时间。 非常小的值并不是最终的结果值,所以需要mtouch.js对外维护一个位置数据,类似:
//真实位置数据;
letdragTrans={x=0,y=0};
//累加mtouch传递过来的增量deltaX和deltaY;
DragTrans.x+=ev.delta.deltaX;
DragTrans.y+=ev.delta.deltaY;
// 通过transform直接操作元素;
设置($drag,dragTrans);
初始位置
为了维护这个外部位置数据,如果像上面那样直接将初始值设置为0,那么将很难正确识别使用css设置了transform属性的元素,这会导致操作元素立即移动到点(0 ,0)开头,所以我们需要初始获取某个元素的真实位置值,然后对其进行维护和操作。 此时,就需要用到我们前面提到的 getCompulatedStyle 方法和matrixTo 函数:
// 获取csstransform属性,此时得到的是一个矩阵数据;
//变换:矩阵(1.41421,1.41421,-1.41421,1.41421,-50,-50);
letstyle=window.getCompulatedStyle(el,null);
letcssTrans=style.transform||style.webkitTransform;
// 根据规则转换得到:
letinitTrans=_.matrixTo(cssTrans);
//{x:-50,y:-50,缩放:2,旋转:45};
//即设置元素:translate:translate(-50px,-50px)scale(2)rotate(45deg);
结语
至此,相信你已经对手势的原理有了基本的了解。 基于这个原理,我们可以封装更多的手势,比如双击、长按、滑动,甚至更酷的三指、四指操作等等,让应用具有更加人性化的特点。
基于以上原则,我封装了几个常用的工具:(求star-.-)
温馨提示:由于仅针对联通,所以需要在联通设备上打开demo,或者在PC上打开移动调试模式!
mtouch.js:中国联通的手势库,封装了以上五个手势,简化的api设计包括常用的手势交互。 基于此,还可以方便地进行扩展。
touchkit.js:基于mtouch封装的与业务更加紧密相关的工具包,可以用来做各种手势操作服务,一键开启,一站式服务。
mcanvas.js:基于canvas开放且极简的API,可以实现图片的一键导入。
谢谢