一、前言

这几个月事情比较多,写了一些博客都没有来得及整理发布,今天刚好有一位同事在开发前端页面的时候用到了手势判断。所以翻出了之前写的 demo,顺便整理一下作为记录。

手势判断在各种应用中都十分常见,如 APP 中的手势翻页,前进后退等等,如微博做得就特别好,微信的话就不想吐槽了。不扯太远,H5 开发中手势判断一般多用于一些交互比较灵活的场景,例如大转盘抽奖游戏,旋转菜单,酷跑,打砖块游戏等等。今天不具体到这些小游戏的开发,我们重点讲讲实现的原理。其实比较基础,大神请自动忽略。

二、实现原理

前提事件,所谓手势,就是你的手对于屏幕触摸的方向或者说轨迹。其实移动端只不过是用户的手指代替了 PC 端的鼠标。所以如果你做过 PC 端页面的鼠标轨迹判断,那移动端的手势判断思路也就可以秒杀了。

上面我们提到,要对用户手指对屏幕的操作进行捕捉,在 H5 中提供了这样几个关键的事件监听 touchstarttouchmovetouchend。因为比较基础我下面就简单带过了。

首先 touchstart 顾名思义 “触摸-开始”,也就是当手指触摸到屏幕的时候将触发这个事件。其次 touchmove “触摸-移动”。也就是当你的手指在屏幕上划动的时候该事件将不断被触发。最后 touchend “触摸-结束” 当手指离开屏幕的那一瞬间触发。

手指事件已经解决了,接下来就是想办法判断用户的手指是怎样划动的了。这里就需要引入一个十分基础的概念坐标系。我们数学上的平面直角坐标系是这样

而我们屏幕上的坐标系是这样的:

注意 Y 轴的增长方向和平面直角坐标系是相反的。

到这里基本上思路就已经确定了,当手指触摸到屏幕的一瞬间也就是 touchstart 时记录手指触摸的位置(xstart,ystart),当手指离开屏幕时也就是 touchend 时获取手指离开屏幕的位置(xend,yend),然后进行判断,如果 xend - xstart > 0 那么水平方向是向右,反之向左。如果 yend - ystart > 0 那么垂直方向是向下,反之向上。当然机器是十分精细的,就算移动一个像素上述判断原则也将成立,人类是没有机器那么敏感的,所以这里顺便提一个东西,也就是灵敏度的问题,一般来说我们是不希望移动一个像素也算是触发了某个手势的,下面讲到代码时会有所体现。

手势情况图解:总共有八个方向

三、代码实现

由于这次的项目已经使用了 zepto.js 这个框架,所以顺手在此基础上给它添加了自己的手势拓展方法。这里我就不再累述给 zepto.js 添加拓展方法的知识点了。还不了解的朋友请先 Google(说到 Google 下篇文章记录一下如何搭建自己的服务器利用 ss 科学上网吧)。我们先上完整代码,在进行核心代码分析

完整代码如下:

$.fn.gesture = function(callback,sensibility){

    if (!sensibility || isNaN(sensibility) || sensibility <= 0)
sensibility = 130; var start_piont,end_point,delta_x,delta_y,distances;
var direction = {
horizontal : null,
vertical : null
} // var cond = 130;//灵敏度控制 this.bind('touchstart',function(e){
var touch = e.touches[0];
start_piont = {
x: touch.pageX,
y: touch.pageY
}
}).bind('touchmove',function(e){
/* var touch = e.touches[0];
end_point = {
x: touch.pageX,
y: touch.pageY
}*/
}).bind('touchend', function(e){ var touch = e.changedTouches[0];
end_point = {
x: touch.pageX,
y: touch.pageY
} delta_x = end_point.x - start_piont.x;
delta_y = end_point.y - start_piont.y; if(delta_x < -sensibility) { // 向左划动
direction.horizontal='left';
} else if (delta_x > sensibility) { // 向右划动
direction.horizontal='right';
}
if(delta_y > sensibility){
direction.vertical = 'down';
}else if(delta_y < (-sensibility)){
direction.vertical = 'up';
}
if(typeof callback === 'function'){
callback(direction);
} //还原状态
direction = {
horizontal : null,
vertical : null
}
});

注意:基于 zepto.js 的拓展方法,所以注意一下依赖。记得先引入 zepto.js

分析:

首先看下下面这句代码

$.fn.gesture = function(callback,sensibility = 130){...}

sensibility = 130 这个是 js 的默认参数值,这个在我之前的文章《开源原生JavaScript插件-CJPCD(省市区联动)》文章中有提到过,后面有点纰漏是 IOS 设备并不兼容,所以这里还是采用了保险的做法:

if (!sensibility || isNaN(sensibility) || sensibility <= 0){
sensibility = 130;
}

sensibility 就是上面提到的灵敏度的问题,笔者默认设置为 130 没为什么。除此之外代码中还出现了另外几个关键的变量。

  • start_piont: 手指开始触摸的点(x,y)
  • end_piont: 手指j结束触摸的点(x,y)
  • delta_x: X 轴的变量增量
  • delta_y: Y 轴的变量增量
  • direction: 方向对象手势判断结果的返回值,包括两个成员
    • horizontal : 水平方向,取值为 nullleftright
    • vertical : 垂直方向,取值为 nullup 或者 down

代码值得注意的是:touchstarttouchmove 都可以通过 e.touches[0] 来获取当前的手指坐标。但是 touchend 中的 touches (本质是 TouchList) 的长度为零 也就是说你无法通过这个来获取到手指离开时那个点的坐标 如下图

那这样需求无法做了?答案是否定的,我们先来看方案一

既然这样我们来 touchmove 事件中做处理,在我们手指在屏幕上划动的时候会不断调用该方法,那么我们可以在该方法中不断得给 end_point 刷新它的坐标值(x,y)那么当我们的手指离开屏幕,也就是不再触发 touchmove 事件,但触发 touchend 事件,此时在 touchend 事件中取得 end_point 的坐标即得到了手指离开屏幕的坐标。按此思路下 代码应该修改如下:

...

this.bind('touchstart',function(e){
var touch = e.touches[0];
start_piont = {
x: touch.pageX,
y: touch.pageY
}
}).bind('touchmove',function(e){
//在这里不断刷新
var touch = e.touches[0];
end_point = {
x: touch.pageX,
y: touch.pageY
}
}).bind('touchend', function(e){ //在这里取值计算
delta_x = end_point.x - start_piont.x;
delta_y = end_point.y - start_piont.y; if(delta_x < -sensibility) { // 向左划动
direction.horizontal='left';
} else if (delta_x > sensibility) { // 向右划动
direction.horizontal='right';
}
if(delta_y > sensibility){
direction.vertical = 'down';
}else if(delta_y < (-sensibility)){
direction.vertical = 'up';
}
if(typeof callback === 'function'){
callback(direction);
} //还原状态
direction = {
horizontal : null,
vertical : null
}
});

这样做确实能解决上面我们提到的问题。但是从性能上来讲在手指划动的时候不断的赋值或者计算绝对不是实现这个需求的最佳方案,虽然在这里并不会有明显的性能问题,但是原则上我们还是不要这样做。所以我们采用下面的方案。 在引出下面方案之前我们先来介绍一下 changedTouches--‘触发事件时改变的触摸点的集合’,划动的点自然符合这个条件,也就是说我们可以通过它来获取到手指离开屏幕时的那个点。不过这里要声明一下,这篇文章不涉及多点触摸的实现,所以我们以单点(一根手指)的触摸为前提继续下面的代码改造:

this.bind('touchstart',function(e){
var touch = e.touches[0];
start_piont = {
x: touch.pageX,
y: touch.pageY
}
}).bind('touchmove',function(e){
// 请注意这里代码清空了
}).bind('touchend', function(e){ //通过 changedTouches 获取手指离开屏幕时的坐标
var touch = e.changedTouches[0];
end_point = {
x: touch.pageX,
y: touch.pageY
} delta_x = end_point.x - start_piont.x;
delta_y = end_point.y - start_piont.y; if(delta_x < -sensibility) { // 向左划动
direction.horizontal='left';
} else if (delta_x > sensibility) { // 向右划动
direction.horizontal='right';
}
if(delta_y > sensibility){
direction.vertical = 'down';
}else if(delta_y < (-sensibility)){
direction.vertical = 'up';
}
if(typeof callback === 'function'){
callback(direction);
} //还原状态
direction = {
horizontal : null,
vertical : null
}
});

到这里我们的组件也算基本编写完成了。调用方式如下

<!--引入依赖-->
<script src="http://cdn.bootcss.com/zepto/1.2.0/zepto.min.js"></script>
<!--引入组件-->
<script src="../plugins/cj-zepto-gesture.min.js"></script>

调用方法:

//使用案例,90 为灵敏度
$('.test-container').gesture(function(direction){
//回调函数处理
console.log('拓展方法',direction);
},90);

test-container 为手势操作容器,如下面的蓝色 div、

限于笔者技术,文章观点难免有不当之处,希望发现问题的朋友帮忙指正,笔者将会及时更新。也请转载的朋友注明文章出处并附上原文链接,以便读者能及时获取到文章更新后的内容,以免误导读者。笔者力求避免写些晦涩难懂的文章(虽然也有人说这样显得高逼格,专业),尽量使用简单的用词和例子来帮助理解。如果表达上有好的建议的话也希望朋友们在评论处指出。

本文为作者原创,转载请注明出处! Cboyce

Zepto 添加手势判断拓展方法(思路+原理)的更多相关文章

  1. UITableViewCell中的UILabel添加手势没有响应的解决方法

    有时候自定义UITableViewCell,且cell中添加了一个UILabel,我们的目的是给该label添加一个手势.但是如果按照常规的添加方法,发现所添加的手势并不能响应.以下为解决方法:将手势 ...

  2. 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理

    [微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...

  3. Android ListView实现不同item的方法和原理分析

    ListView实现不同item的方法和原理分析 一问题抛出Listview是android里面的重要组件,用来显示一个竖向列表,这个没有什么问题:但是有个时候列表里面的item不是一样的,如下图,列 ...

  4. 网页中给超链接添加"是否确认"的方法

    最近在做数据库, 需要给一个"删除"链接增加是否确认的弹出框, 在网上查到了两种方法: 1, 先看看最麻烦的一种 <html xmlns="http://www.w ...

  5. 给UIView添加手势

    对于不能addTarget的UI对象,添加手势为他们带来了“福音”,以为UIView添加手势为例,揭开手势的面目. 1,创建一个view先, UIView * jrView=[[UIViewalloc ...

  6. JS跨域方法及原理

        JS跨域分析判断 JS跨域:在不同域之间,JS进行数据传输或通信.比如ajax向不同的域请求数据.JS获取iframe中的页面中的值(iframe内外不同域) 只要协议.端口.域名有一个不同则 ...

  7. 反射 DataTable拓展方法 转实体对象、实体集合、JSON

    Mapper类 using System; using System.Collections.Generic; using System.Data; using System.Globalizatio ...

  8. xib添加手势后报错:-[UITapGestureRecognizer setFrame:]: unrecognized selector sent to instance xxx

    主要原因如下: + (instancetype)mineHeaderView { return [[NSBundle mainBundle] loadNibNamed:@"DDMineHea ...

  9. jQuery插件实现的方法和原理简单说明

    下文来自 http://www.itzhai.com/jquery-plug-ins-to-achieve-the-methods-and-principles-of-simple-instructi ...

随机推荐

  1. [ZooKeeper.net] 1 模仿dubbo实现一个简要的http服务的注册 基于webapi

    今天来试着模仿下dubbo实现一个简要的http服务的注册,虽说是模仿不过是很廉价的那种,只是模仿了一点点点...... 先放上demo目录结构: 开头还是把ZooKeeper的一些简要介绍搬过来看看 ...

  2. 巧用*_his表记录操作历史

    文章转载自「开发者圆桌」一个关于开发者入门.进阶.踩坑的微信公众号 许多OLTP应用的开发者都知道,一些重要的操作要记录操作历史,把操作前的数据备份到历史表,然后再执行相应的修改操作.这样可以获取某个 ...

  3. MySQL分区表的局限和限制

    禁止构建 分区表达式不支持以下几种构建: 存储过程,存储函数,UDFS或者插件 声明变量或者用户变量 可以参考分区不支持的SQL函数 算术和逻辑运算符 分区表达式支持+,-,*算术运算,但是不支持DI ...

  4. node c++多线程插件构想

    最近想写一个node的c++插件实现线程.提供的api使用回调并进行二次包装使其返回一个promise,并且要求需要在工作线程里执行的函数为async函数.如果是node7.0以下的版本,函数必须返回 ...

  5. 2620: [Usaco2012 Mar]Haybale Restacking

    2620: [Usaco2012 Mar]Haybale Restacking Time Limit: 5 Sec  Memory Limit: 128 MBSubmit: 201  Solved:  ...

  6. Oralce Plsql 中文显示乱码问题无需修改注册表完美解决

    此方法在其它版系统也可以解决 win10-64 plsql oracle 11g绿色版客户端 错误现象: 因为oracle用的是绿色版,无法设置注册表,尝试以下方法. 解决方法: 1.在plsql中查 ...

  7. 使用git克隆指定分支的代码

    今天想学习一下开源中国Android客户端的app源码,源码的Git地址:http://git.oschina.net/oschina/android-app,如下图所示: 由于Master主分支上没 ...

  8. 【G】开源的分布式部署解决方案文档 - 手动安装

    G.系列导航 [G]开源的分布式部署解决方案 - 导航 序言 因各种原因,决定先写使用文档.也证明下项目没有太监.至于安装过程复杂,是因为还没有做一键安装,这个现阶段确实没精力. 项目进度 (点击图片 ...

  9. Unity 3D Framework Designing(1)—— MVVM 模式的设计和实施(Part 1)

    初识 MVVM 谈起 MVVM 设计模式,可能第一映像你会想到 WPF/Sliverlight,他们提供了的数据绑定(Data Binding),命令(Command)等功能,这让 MVVM 模式得到 ...

  10. 记一次解析XML转对象的笔记

    项目中调用第三方API,返回格式是XML字符串,需要将XML反序列化为对象,格式如下: <?xml version="1.0"?> <Response xmlns ...