更新 : 2019-03-22
以前以为 hammer 的触发顺序是 parent -> child 和我们常用的 js 冒泡相反 .
今天才知道原来 hammer 根本没有冒泡或者捕获的概念,你先绑定哪一个事件它就触发哪一个. 晕 ~
hammer 还有一个看上去好像 bug 的问题
<div id="keat" class="keat"> <div id="left" class="left"></div> <div id="right" class="right"></div> </div>
const left = new Hammer(document.getElementById('left')); left.on('panstart', (e: HammerInput) => console.log(e.target)); const keat = new Hammer(document.getElementById('keat')); keat.on('panstart', (e: HammerInput) => console.log(e.target));
如果我们起始点是 left 然后 10px 后在 right, 这时 hammer 触发获取到的 e.target 会有 2 个.
left 获取到 left, keat 获取到 right
如果我们依赖 e.target 做判断要不要触发 event handler 可能就会遇到坑了. 目前没有办法解决. 我是想办法让用户避开这种交互.
更新 : 2019-03-12
double tap
var manager = new Hammer(el); const tap = manager.get('tap'); manager.remove('tap'); var doubleTap = new Hammer.Tap({ event: 'doubletap', taps: 2 }); manager.add([doubleTap, tap]); tap.requireFailure(doubleTap); doubleTap.recognizeWith(tap); manager.on('doubletap', function (e) { console.log('doubletap'); }); manager.on('tap', function (e) { console.log('tap'); });
留意 manager.add array 的顺序, 反过来就不触发了.
double tap 和 tap 要互相认识一下. 因为彼此是有影响的.
更新 : 2018-01-31 (hammer 的坑)
hammer 的 pinch 在某种情况下会自动触发 panEnd,很奇葩.
解决方法就是记入时间呗
hammer 有鬼, ghost click
如果你使用 pan + click 你会发现触发是这样的 panStart->panMove->panEnd->click
这个 browser 的行为有点不同
browser 是 mousedown -> mouseup -> click 或者 mousedown -> mousemove -> 没了
只要有 move click 事件是不会触发的.
所以这点要特别留意, 如果你不希望这个 ghost click 发生, hammer 也给出了方法
refer :
http://hammerjs.github.io/tips/
https://gist.github.com/jtangelder/361052976f044200ea17
就是在 click 时做一个判断.
另外提一个奇葩场景,如果你 bind 了 dragStart 然后 event.preventDefault 那么这个 ghostclick 是会被取消的, 但是 dragStart 是否每一次都会触发很难说 (我们看过 hammer 源码,不过我觉得它视乎动了点手脚).
更新 : 2018-01-18
今天开发的时候遇到了一些奇葩形象.
看了看源码理清一下之前没有讲清楚的点
1. ng 有 3 个 Event Plugin (自己想扩展多几个是 ok 的), Dom Event Plugin, KeyBoard Event Plugin, HammerGesture Plugin
2. 所有的 plugin in 都必须在 app 级的 providers 去提供, lazy load 就迟了 (refer : https://github.com/angular/material2/issues/7905 and https://github.com/angular/angular/issues/19874)
3. angular material 的 MatSliderModule 会去覆盖 hammer 的 config ( 和我以前说到的方式是一样的 ) .
所以问题来了.
1. 你要 angular material 的 MatSliderModule 的手势 work good, 那么就一定要在 app 级注入这个模块, 或者提供它的手势 override 在 app providers (原因是上面第 2 条)
2. 如果你自己有一套 hammer config 逻辑, 那么注定会和 material 的 config 打架 ( 上面的 第 3 条 ), ng 只运行一个 hammer config
怎么破 ?
目前没有什么好方法. 大费周章的做法是, 开发一套 MyHammerGesture Plugin 并且把监听 (pan)="do()" 换成 (myPan)="do()" 这样就不会和它们打架咯.
material team 给出了一个方向
https://github.com/angular/material2/issues/4595
大致上就是他们以后会使用 service, 我个人觉得把 config 让出来给 app 是对的,如果我们写自己的库也应该自己封装 hammer 而不是用掉只有一个的 config.
更新 : 2017-12-28
hammer 的绑定流程是, 选择 element -> 实例化 hammer -> 设置 hammer (default 有些设置是关上的) -> 绑定监听事件.
pinch 和 rotate 设置默认是关上的, 想监听就要先打开设置, 这 2 个事件 hammer 会使用 css touch-action : none 来禁止游览器处理 touch (意思是说不能 scroll 了)
hammertime.get('pinch').set({ enable: true });
hammertime.get('rotate').set({ enable: true });
pan 和 swipe 默认设置是处理 horizontal 横向而已, 直的话是游览器的 scroll, touch-action : pan-y, 如果要监听多点就打开设置
hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL });
hammertime.get('swipe').set({ direction: Hammer.DIRECTION_VERTICAL });
以上是 hammer 的基本处理思路, 默认情况下让游览器可以 scroll. 要处理更多就自己设置吧.
ng 方面. 之前(看下面) 有提到过了. 所有的手势 ng 都会开启 hammer 的 pinch 和 rotate 设置, 也就是说即使你只是写了一个 (tap) 事件, 你的 element 就不能 scroll 了.
为什么 ng 要这样呢, 我也不知道. 可能懒得去理会吧. 全部开启设置, 才能统一方式绑定 (pinch) 和 (rotate) 嘛.
一直没有好的解决方法 ng 没什么接口方便我们覆盖这个逻辑
refer : https://medium.com/@TheLarkInn/creating-custom-dom-events-in-angular2-f326d348dc8b (ng 自定义事件绑定)
ng 的 dom event 源码
https://github.com/angular/angular/blob/master/packages/platform-browser/src/dom/events/dom_events.ts
https://github.com/angular/angular/blob/master/packages/platform-browser/src/dom/events/event_manager.ts
https://github.com/angular/angular/blob/master/packages/platform-browser/src/dom/events/hammer_gestures.ts
https://github.com/angular/angular/blob/master/packages/platform-browser/src/dom/events/key_events.ts
几个方法参考 :
1. 直接调用 Hammer 不用 ng 的绑定
2. 覆盖 HammerGestureConfig.buildHammer 方法, 这个方法只能获取到 element, 所以我们通过在 element 上面写 data-need-rotate or data-need-pinch 之类的表达, 来实现不同情况下不同的 hammer 设置
3. 覆盖 HammerGesturesPlugin 来个统统重写...
我目前用了第 2 个 方案. 暂时挺着吧.
更新 22-08-2017
hammerjs 的事件有不同的设置 options
var myElement = document.getElementById('myElement'); var mc = new Hammer(myElement); mc.get('pan').set({ direction: Hammer.DIRECTION_ALL }); mc.on("panleft panright panup pandown tap press", function(ev) { myElement.textContent = ev.type +" gesture detected."; });
pan 事件默认的 direction 是没有包含上下移动的, 那我们可以自己去设置它.
而 Angular 默认会帮我们把所有的 hammer 都设置一遍如下的代码
这 2 个设置会让所有的 hammer element 无法滚动. 这个要注意哦.
我们可以通过 overrides 去添加和覆盖上面的逻辑.
providers: [{ provide: HAMMER_GESTURE_CONFIG, useClass: MyHammerConfig }] export class MyHammerConfig extends HammerGestureConfig { overrides = <any>{ 'pan': { direction : Hammer.DIRECTION_ALL } } }
大概这样就可以了,不过呢这个 config 是全局的. 目前我还没发放可以针对不同的 element 设置不同的 config .
可能需要直接调用 hammerjs 才可以了。
refer :
http://hammerjs.github.io/
https://bevacqua.github.io/dragula/
手机 和 PC 在交互体验上最大的区别是交互工具不同.
PC端,我们用滑鼠
手机端, 我们用触屏
滑鼠 vs 触屏
滑鼠有 hover 概念, 触屏没有.
触屏能多点 (多种手势), 滑鼠没有.
responsive design 解决了视觉上的差异问题,却没有解决交互上的差异问题.
hammerjs 帮我们解决的是滑鼠, 触屏之前的差异问题.
比如 : 监听各种手势, click 的 delay 300ms 问题, touchmove 模拟成 mousemove 事件等等.
angular 认可 hammerjs, 所以只要你 import hammerjs, 你可以直接这样写
<div (tap)="test()" > test </div>
angular 会使用 hammerjs 的方法来绑定 tap 事件. 很方便吧 ?
使用 npm 加载
"dependencies": { "hammer-timejs": "^1.1.0", "hammer-touchemulator": "0.0.2", "hammerjs": "^2.0.8", }, "devDependencies": { "@types/hammerjs": "^2.0.34", }
在 main.ts 里 import 就可以啦
import 'hammerjs'; import 'hammer-timejs'; import * as TouchEmulator from 'hammer-touchemulator'; TouchEmulator();
TouchEmulator 是 development 情况下的才使用的.
手机还有一个问题就是在 drag and drop. 原生游览器的 drag and drop 在手机端支持的不好.
很多人会用 touchmove 来模拟. 也就是 hammerjs 里面的 pan 事件.
但是具体做法还是挺困难的.
因为触屏没有 hover 概念, 也没 touchover 事件, 所以当用户 pan 的时候你并没有办法轻易的监听 dragover 来处理事情. (比如你要做 sorting)
我在网上看了一些人的做法是通过 document.elementFromPoint(x,y) 来获取交会的节点或则每一次 pan 的时候都通过 service 发布事件, 然后其它组件监听, 在依据自己的 element.getBoundingClientRect 的
position 来确定是否 over 到了 element. 简单就说就是模拟 dragover 事件.
如果你有好方法欢迎你留意告诉我哦.