irpas技术客

用 JavaScript 实现手势库 — 手势逻辑【前端组件化】_三钻 - 之前端到全栈之路

未知 461

前端组件化系列目录 「一」用 JSX 建立组件 Parser(解析器)「二」使用 JSX 建立 Markup 组件风格「三」用 JSX 实现 Carousel 轮播组件「四」用 JavaScript 实现时间轴与动画「五」用 JavaScript 实现手势库 - 实现监听逻辑「六」用 JavaScript 实现手势库 — 手势逻辑 《 本期 》… 待续 …

上一期《实现监听逻辑》中我们一起实现了基础的手势监听逻辑。有了这些手势的监听后,我们就可以开始实现每一个手势的逻辑。最终我们可以把这些手势应用到我们的《轮播图组件》当中。

接下来我们就开始实现 gesture 的逻辑。


Start 事件

首先我们会触发一个 start 事件,也就是当我们手指触摸到屏幕时第一个触发的事件。这时会有三种情况:

手指松开 会触发 end 事件,这样就构成一个 tap 点击的行为通过监听 end 事件来实现即可 手指拖动超过 10 px 这种就是 pan start 拖动的行为我们可以在 move 事件判断当前与上一个触点的距离 手指停留在当前位置超过 0.5s 这种就是 press start 按压的行为我们可以添加一个 setTimeout 来实现
Press 事件

所以我们第一步就是在 start 函数中加入一个 setTimout 的 handler 处理程序。

let handler; let start = point => { handler = setTimeout(() => { console.log('presss '); }, 500); };

一般来说 press 是我们比较常见的一个行为。但是实际上这里是 press start 事件,后面还会跟随着一个 press end 的事件。我们也可以统称这个为 press 事件,然后这个手势库的使用者只需要监听这个 press 事件即可,极少的情况下是需要监听 press end 事件的。

这里我们需要注意的是,当我们触发其他的事件的时候,这个 500 毫秒的 setTimout 是有可能会被取消掉的。所以我们需要给这段逻辑一个 handler,并且放在全局作用域中,让其他事件可以获取到这个变量,并且可使用它取消掉这个处理逻辑。


Pan 事件

接下来我们就去监听移动 10px 的 pan 事件,这里就需要我们记录一开始用户触摸屏幕时的 x 和 y 坐标,当用户移动手指的时候,持续计算新移动到的位置与初始位置的距离。如果这个距离超过了 10px 就可以触发我们的 pan start 的事件了。

所以首先我们需要在 start 函数中加入 startX 和 startY 的坐标记录,这里要注意的是,因为这两个值都是会在多个地方被使用的,所以也是需要在全局作用域中声明。

然后在 move 函数中计算当前触点与起点的直径距离。这里我们需要用到数学中的直径运算公式 x 2 + y 2 = z 2 x^2 + y^2 = z^2 x2+y2=z2,而这里面的 x 是 当前触点的 x 坐标 - 起点的 x 坐标 的 x 轴的距离, y 就是 当前出点的 y 坐标 - 起点的 y 坐标 运算出来的 y 轴的距离。最终两个距离二次幂相加就是直径距离的二次幂。

在代码中我们一般都会尽量避免使用根号运算,因为根号运算会对性能有一定的影响。我们知道最终要判断的是直径距离是否是大于一个固定的 10px。那就是说 z = 10,而 z 的二次幂就是 100,所以我们直接判断这个直径距离是否大于 100 即可。

这里还有一个需要注意的,就是当我们手指移动超过 10px 之后,如果我们手指没有离开屏幕而是往回移动了,这样的话我们距离起点已经不够 10px了。但是这个其实也是算 pan 事件,因为我们确实有移动超过 10px 距离,超过这个距离之后所有的移动都是属于 pan 事件。

所以我们需要一个 isPan 的状态,第一次移动超出 10px 的时候,就会触发 pan-start 事件,并且把 isPan 置为 true,而后面的所有移动都会触发 pan 事件。

根据我们上面讲到的 press 事件,如果我们按下手指后 0.5 秒内出现了移动,那么 press 事件就会被取消。所以这里我们就需要 clearTimeout 把 pressstart 的 handler 给清楚掉。

let handler; let startX, startY; let isPan = false; let start = point => { (startX = point.clientX), (startY = point.clientY); isPan = false; handler = setTimeout(() => { console.log('pressstart'); }, 500); }; let move = point => { let dx = point.clientX - startX, dy = point.clientY - startY; let d = dx ** 2 + dy ** 2; if (!isPan && d > 100) { isPan = true; console.log('pan-start'); clearTimeout(handler); } if (isPan) { console.log(dx, dy); console.log('pan'); } };
Tap 事件

Tap 的这个逻辑我们可以在 end 事件里面去检查。首先我们默认有一个 isTap 等于 true 的状态,如果我们触发了 pan 事件的话,那就不会去触发 tap 的逻辑了,所以 tap 和 pan 是互斥的关系。但是为了不让它们变得很耦合,所以我们不使用原有的 isPan 作为判断状态,而是另外声明一个 isTap 的状态来记录。

这里我们 tap 和 pan 都有单独的状态,那么我们 press 也不例外,所以也给 press 加上一个 isPress 的状态,它的默认值是 false。如果我们 0.5 秒的定时器被触发了,isPress 也就会变成 true。

既然我们给每个事件都加入了状态,那么这里我们就给每一个事件触发的时候设置好这些状态的值。

press 时 isTap = falseisPan = falseisPress = true pan 时 isTap = falseisPan = trueisPress = false tap 时 isTap = trueisPan = falseisPress = false

如果我们发现用户没有移动,也没有按住触屏超过 0.5 秒,当用户离开屏幕时就会调用 end 函数,这个时候我们就可以认定用户的操作就是 tap。这里我们要注意的是,我们 press 的 0.5 秒定时器是没有被关闭的,所以我们在 isTap 的逻辑中需要 clearTimeout(handler)。

说到取消 press 定时器,其实我们 handler 的回调函数中,也需要做一个保护代码逻辑,在触发了 press-start 之后,我们需要保证每次点击屏幕只会触发一次,所以在 setTimout 的回调函数中的最后,我们需要加上 handler = null。这样只要 press-start 触发了,就不会再被触发。

let handler; let startX, startY; let isPan = false, isPress = false, isTap = false; let start = point => { (startX = point.clientX), (startY = point.clientY); isPan = false; isTap = true; isPress = false; handler = setTimeout(() => { isPan = false; isTap = false; isPress = true; console.log('press-start'); handler = null; }, 500); }; let move = point => { let dx = point.clientX - startX, dy = point.clientY - startY; let d = dx ** 2 + dy ** 2; if (!isPan && d > 100) { isPan = true; isTap = false; isPress = false; console.log('pan-start'); clearTimeout(handler); } if (isPan) { console.log(dx, dy); console.log('pan'); } }; let end = point => { if (isTap) { console.log('tap'); clearTimeout(handler); } };
End 事件

到了最后这里我们要处理的就是所有的结束时间,包括 press-end 和 pan-end。

这两个 end 事件都会在 end 函数中判断所得,如果在用户操作的过程中触发了 pan-start 或者 press-start 事件,到了 end 函数这里,对应的状态就会是 true。

所以我们对 end 函数做了以下改造:

let end = point => { if (isTap) { console.log('tap'); clearTimeout(handler); } if (isPan) { console.log('pan-end'); } if (isPress) { console.log('press-end'); } };

最后我们需要在 cancel 事件触发的时候,清楚掉 press 事件的 setTimeout。既然我们的操作被打断了,那也不可能会触发我们的长按事件了。

// 加入 cancel let cancel = point => { clearTimeout(handler); console.log('cancel'); };

我们除了 flick 的逻辑,我们已经完成所有手势库里面的事件了。并且也能正确的区分这几种手势操作了。


这期我们就先到这里啦,下期我们就来一起完整这个手势库的逻辑,并且重新整理一次这里面的状态!~ 敬请期待,记得持续关注三哥哦~

我是来自《技术银河》的三钻,一位正在重塑知识的技术人。下期再见。


?? 三哥推荐 开源项目推荐 Hexo Theme Aurora

在最近更新到版本 1.5.0,包含以下内容:

》预览《

? 新增

自适应 “推荐文章” 布局 (增加了一个新的 “置顶文章布局” !!) 能够在“推荐文章”和“置顶文章”模式之间自由切换如果总文章少于 3 篇,将自动切换到“置顶文章”模式在文章卡上添加了“置顶”和“推荐”标签📖 文档 增加了与 VuePress 一样的自定义容器 #77 Info 容器Warning 容器Danger 容器Detail 容器预览 支持了更多的 SEO meta 数据 #76 添加了 description添加了 keywords添加了 author📖 文档

最近博主在全面投入开发一个可以 “迈向未来的” Hexo 主题,以极光为主题的博客主题。

如果你是一个开发者,做一个个人博客也是你简历上的一个亮光点。而如果你有一个超级炫酷的博客,那就更加是亮上加亮了,简直就闪闪发光。

如果喜欢这个主题,可以在 Github 上给我点个 🌟 让彼此都发光吧~

主题 Github 地址:https://github.com/auroral-ui/hexo-theme-aurora 主题使用文档:https://aurora.tridiamond.tech/zh/


VSCode Aurora Future

对,博主还做了一个 Aurora 的 VSCode 主题。用了Hexo Theme Aurora 相对应的颜色色系。这个主题的重点特性的就只用了 3 个颜色,减少在写代码的时候被多色多彩的颜色所转移了你的注意力,让你更集中在写代码之中。

喜欢的大家可以支持一下哦! 直接在 VSCode 的插件搜索中输入 “Aurora Future” 即可找到这个主题哦!~

主题 Github 地址:https://github.com/auroral-ui/aurora-future-vscode-theme 主题插件地址:https://marketplace.visualstudio.com/items?itemName=auroral-ui.aurora-future


Firefox Aurora Future

我不知道大家,但是最近我在用火狐浏览器来做开发了。个人觉得火狐还真的是不错的。推荐大家尝试一下。

当然我这里想给大家介绍的是我在火狐也做了一个 Aurora 主题。对的!用的是同一套的颜色体系。喜欢的小伙伴可以试一下哦!

主题地址:https://addons.mozilla.org/en-US/firefox/addon/aurora-future/


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: # #JavaScript #实现手势库 #手势逻辑前端组件化 #gesture