any-touch
Advanced tools
Comparing version 0.0.2 to 0.0.3
@@ -6,4 +6,6 @@ module.exports = { | ||
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1' | ||
} | ||
// collectCoverage:true | ||
}, | ||
collectCoverage: true, | ||
coverageDirectory: "./coverage/", | ||
verbose:false, | ||
}; |
{ | ||
"name": "any-touch", | ||
"version": "0.0.2", | ||
"description": "一个纯粹的手势库", | ||
"version": "0.0.3", | ||
"description": "一个手势库", | ||
"main": "esm/main.js", | ||
"scripts": { | ||
"dev": "parcel index.html", | ||
"build": "tsc && rollup -c && rimraf esm/", | ||
"release": "npm run build && npm pub && git push", | ||
"build": "rimraf dist && rollup -c", | ||
"watch": "rimraf dist && rollup -c --watch", | ||
"test": "jest" | ||
@@ -24,11 +25,13 @@ }, | ||
"jest": "^23.6.0", | ||
"parcel-bundler": "^1.10.3", | ||
"rollup": "^0.62.0", | ||
"rollup-plugin-babel": "^4.0.3", | ||
"mini-events": "^0.0.1", | ||
"rimraf": "^2.6.2", | ||
"rollup": "^0.67.1", | ||
"rollup-plugin-typescript": "^1.0.0", | ||
"ts-jest": "^23.10.4", | ||
"tslib": "^1.9.3", | ||
"typescript": "^3.0.1" | ||
}, | ||
"dependencies": { | ||
"setTimeout": "^0.4.9" | ||
"mini-events": "^0.0.2" | ||
} | ||
} |
@@ -1,7 +0,41 @@ | ||
# any-touch | ||
一个手势库 | ||
# any-touch [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![codecov](https://codecov.io/gh/383514580/any-touch/branch/develop/graph/badge.svg)](https://codecov.io/gh/383514580/any-touch) [![CircleCI](https://circleci.com/gh/383514580/any-touch/tree/develop.svg?style=svg)](https://circleci.com/gh/383514580/any-touch/tree/develop) | ||
开发中... | ||
:wave: 一个手势库 | ||
### 已完成 | ||
[npm-image]: https://img.shields.io/npm/v/any-touch.svg | ||
[npm-url]: https://npmjs.org/package/any-touch | ||
[downloads-image]: https://img.shields.io/npm/dm/any-touch.svg | ||
[downloads-url]: https://npmjs.org/package/any-touch | ||
## 安装 | ||
```javascript | ||
npm i -S any-touch | ||
``` | ||
## 使用 | ||
```javascript | ||
import AnyTouch from 'any-touch'; | ||
// 初始化 | ||
const el = doucument.getElementById('gesture-box'); | ||
const at = new AnyTouch(el); | ||
// 绑定手势 | ||
at.on('pan', ev=>{ | ||
console.log(ev.deltaX); | ||
}) | ||
// 解除绑定 | ||
at.off('pan') | ||
// 销毁 | ||
at.destory(); | ||
``` | ||
## 支持 | ||
- [x] 支持手势: tap | doubletap | pan | swipe | pinch | rotate. | ||
@@ -11,3 +45,4 @@ - [x] 支持鼠标(mouse) | ||
### 待完成 | ||
## 计划支持 | ||
- [ ] 识别input数据变形(transform) | ||
@@ -17,2 +52,1 @@ - [ ] 自定义识别器(recgnizer) | ||
- [ ] 自定义任意触点,让鼠标支持rotate和pinch(addPointer) | ||
- [ ] 单元测试(test) |
@@ -1,6 +0,8 @@ | ||
import babel from 'rollup-plugin-babel'; | ||
// import babel from 'rollup-plugin-babel'; | ||
import typescript from 'rollup-plugin-typescript'; | ||
export default { | ||
input: 'esm/main.js', | ||
plugins: [babel({ | ||
exclude: 'node_modules/**' | ||
input: './src/main.ts', | ||
plugins: [typescript({ | ||
exclude: 'node_modules/**', | ||
typescript: require('typescript') | ||
})], | ||
@@ -7,0 +9,0 @@ output: [{ |
@@ -23,3 +23,3 @@ // 返回最近一个时间段的计算结果 | ||
// _prevInput || input用来保证start阶段也有prevInput, 当然这时等于input | ||
// _prevInput || input用来保证deltaX等不会有undefined参与计算 | ||
_prevInput = _prevInput || input; | ||
@@ -29,3 +29,2 @@ const deltaTime = input.timestamp - _prevInput.timestamp; | ||
const deltaY = (0 < input.centerY) ? input.centerY - _prevInput.centerY : 0; | ||
// 每25ms刷新速度数据 | ||
@@ -46,5 +45,8 @@ if (COMPUTE_INTERVAL < deltaTime) { | ||
} | ||
// 取xy方向2者的最大值 | ||
const maxVelocity = Math.max(velocityX, velocityY); | ||
// console.log({deltaX,deltaY,direction}); | ||
return { velocity: maxVelocity, velocityX, velocityY, direction }; | ||
}; |
@@ -54,3 +54,3 @@ | ||
deltaAngle: 0, | ||
scale: 1, | ||
scale: undefined, | ||
deltaScale: 1, | ||
@@ -99,4 +99,4 @@ lastVelocity: undefined, | ||
// 多点计算 | ||
// 上一触点数大于1, 当前触点大于1 | ||
if (undefined !== prevInput && 1 < prevInput.pointers.length && 1 < input.pointers.length) { | ||
// if(undefined !== startMutliInput){ | ||
// 2指形成的向量 | ||
@@ -106,9 +106,9 @@ const startV = computeVector(startMutliInput); | ||
const activeV = computeVector(input); | ||
// 计算缩放 | ||
const { scale, deltaScale } = computeScale({ | ||
startV, prevV, activeV | ||
const { deltaScale, scale } = computeScale({ | ||
startV, activeV, prevV | ||
}); | ||
computed.scale = scale; | ||
computed.deltaScale = deltaScale; | ||
// console.log({scale, deltaScale}); | ||
@@ -115,0 +115,0 @@ // 计算旋转角度 |
@@ -28,4 +28,2 @@ // 简单判断是否手机设备 | ||
// 识别tap允许最大的移动距离 | ||
export const MAX_MOVE_OF_TAP = 2; | ||
@@ -46,5 +44,5 @@ // 计算时候取touchs.clientX | clientY | ||
// input的类型 | ||
export const INPUT_START = 'input-start'; | ||
export const INPUT_MOVE = 'input-move'; | ||
export const INPUT_CHANGE_LENGTH = 'input-change-length'; | ||
export const INPUT_END = 'input-end'; | ||
export const INPUT_START = 'start'; | ||
export const INPUT_MOVE = 'move'; | ||
export const INPUT_CANCEL = 'cancel'; | ||
export const INPUT_END = 'end'; |
@@ -34,3 +34,3 @@ // 事件的回调 | ||
*/ | ||
dispatch(eventName: string, payload: object): void { | ||
emit(eventName: string, payload: object): void { | ||
if (undefined !== this._stack[eventName]) { | ||
@@ -72,3 +72,3 @@ const callbacks = this._stack[eventName]; | ||
if (undefined === callback) { | ||
events = []; | ||
this._stack[eventName] = []; | ||
} else { | ||
@@ -75,0 +75,0 @@ for (let i = 0, len = events.length; i < len; i++) { |
@@ -30,3 +30,3 @@ let prevPointers: any[] = undefined; | ||
return { | ||
type: `input-${MAP[type]}`, | ||
// type: `input-${MAP[type]}`, | ||
inputStatus: MAP[type], | ||
@@ -33,0 +33,0 @@ changedPointers, |
@@ -6,5 +6,3 @@ export default (event: TouchEvent): any => { | ||
const inputStatus = event.type.replace('touch', ''); | ||
const type = `input-${inputStatus}`; | ||
return { | ||
type, | ||
inputStatus, | ||
@@ -11,0 +9,0 @@ changedPointers, |
@@ -5,3 +5,3 @@ /** | ||
import { Input } from '../interface'; | ||
import { SUPPORT_ONLY_TOUCH, IS_MOBILE, INPUT_CHANGE_LENGTH, INPUT_END, INPUT_MOVE, INPUT_START } from '../const'; | ||
import { SUPPORT_ONLY_TOUCH, IS_MOBILE, INPUT_CANCEL, INPUT_END, INPUT_MOVE, INPUT_START } from '../const'; | ||
import { getCenter } from '../vector'; | ||
@@ -26,4 +26,3 @@ import touchAdapter from './adapters/touch' | ||
} | ||
const { type, pointers, changedPointers } = input; | ||
const { inputStatus, pointers, changedPointers } = input; | ||
// 当前触点数 | ||
@@ -34,4 +33,4 @@ const pointerLength: number = pointers.length; | ||
const changedPointerLength: number = changedPointers.length; | ||
const isFirst = (INPUT_START === type) && (0 === changedPointerLength - pointerLength); | ||
const isFinal = (INPUT_END === type) && (0 === pointerLength); | ||
const isFirst = (INPUT_START === inputStatus) && (0 === changedPointerLength - pointerLength); | ||
const isFinal = (INPUT_END === inputStatus) && (0 === pointerLength); | ||
@@ -58,3 +57,5 @@ // 中心坐标 | ||
const preventDefault = () => { | ||
event.preventDefault(); | ||
if (event.cancelable) { | ||
event.preventDefault(); | ||
} | ||
}; | ||
@@ -61,0 +62,0 @@ |
export type directionString = 'up' | 'right' | 'down' | 'left' | 'none' | 'all'; | ||
export type inputStatus = 'start' | 'move' | 'end' | 'cancel'; | ||
export type RecognizerStatus = 'unknown' | 'recognized' | 'began' | 'changed' | 'ended' | 'failed' | 'cancelled'; | ||
export type RecognizerStatus = 'possible' | 'recognized' | 'began' | 'changed' | 'ended' | 'failed' | 'cancelled'; | ||
@@ -6,0 +6,0 @@ export interface Input { |
124
src/main.ts
@@ -23,10 +23,2 @@ /** | ||
IS_MOBILE, | ||
DIRECTION_NONE, | ||
DIRECTION_LEFT, | ||
DIRECTION_RIGHT, | ||
DIRECTION_UP, | ||
DIRECTION_DOWN, | ||
DIRECTION_HORIZONTAL, | ||
DIRECTION_VERTICAL, | ||
DIRECTION_ALL | ||
} from './const'; | ||
@@ -36,2 +28,3 @@ import EventBus from './EventBus'; | ||
import compute from './compute/index'; | ||
import computeTouchAction from './untils/computeTouchAction' | ||
@@ -44,3 +37,10 @@ import TapRecognizer from './recognitions/Tap'; | ||
import RotateRecognizer from './recognitions/Rotate'; | ||
interface Options { | ||
touchAction?: 'compute' | 'auto' | 'manipulation' | 'pan-x' | 'pan-y' | 'none'; | ||
domEvents?: boolean; | ||
}; | ||
const DEFAULT_OPTIONS: Options = { | ||
touchAction: 'compute', | ||
domEvents: true | ||
}; | ||
export default class AnyTouch { | ||
@@ -53,13 +53,5 @@ static TapRecognizer = TapRecognizer; | ||
static RotateRecognizer = RotateRecognizer; | ||
static DIRECTION_NONE = DIRECTION_NONE; | ||
static DIRECTION_UP = DIRECTION_UP; | ||
static DIRECTION_RIGHT = DIRECTION_RIGHT; | ||
static DIRECTION_DOWN = DIRECTION_DOWN; | ||
static DIRECTION_LEFT = DIRECTION_LEFT; | ||
static DIRECTION_HORIZONTAL = DIRECTION_LEFT | DIRECTION_RIGHT; | ||
static DIRECTION_VERTICAL = DIRECTION_UP | DIRECTION_DOWN; | ||
static DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL; | ||
// 目标元素 | ||
$el: Element; | ||
el: HTMLElement; | ||
@@ -69,3 +61,3 @@ // 各个手势对应的handle集合 | ||
recognizers: any[]; | ||
recognizers: { [propName: string]: any, name: string }[]; | ||
@@ -78,2 +70,3 @@ unbinders: any[]; | ||
options: Options; | ||
/** | ||
@@ -83,20 +76,48 @@ * @param {Element} el | ||
*/ | ||
constructor(el: Element, { | ||
} = {}) { | ||
constructor(el: HTMLElement, options: Options = DEFAULT_OPTIONS) { | ||
this.version = '0.0.2'; | ||
this.el = el; | ||
this.isMobile = IS_MOBILE; | ||
this.eventBus = new EventBus(el); | ||
this.options = { ...DEFAULT_OPTIONS, ...options }; | ||
this.recognizers = [ | ||
new TapRecognizer({ name: 'tap', pointer: 1, taps: 1 }), | ||
new PressRecognizer({ name: 'press' }), | ||
new PanRecognizer({ name: 'pan' }), | ||
new SwipeRecognizer({ name: 'swipe' }), | ||
new PinchRecognizer({ name: 'pinch' }), | ||
new RotateRecognizer({ name: 'rotate' }), | ||
new TapRecognizer(), | ||
new PressRecognizer(), | ||
new PanRecognizer(), | ||
new SwipeRecognizer(), | ||
new PinchRecognizer(), | ||
new RotateRecognizer(), | ||
]; | ||
this.recognizers.forEach(recognizer => { | ||
recognizer.injectUpdate(this._update.bind(this)); | ||
}); | ||
// 计算touch-action | ||
this.setTouchAction(el); | ||
}; | ||
// 绑定事件 | ||
this.unbinders = this._bindRecognizers(el); | ||
/** | ||
* 计算touch-action | ||
* @param {HTMLElement} 目标元素 | ||
*/ | ||
public setTouchAction(el: HTMLElement) { | ||
if ('compute' === this.options.touchAction) { | ||
let touchActions = []; | ||
for (let recognizer of this.recognizers) { | ||
touchActions.push(...recognizer.getTouchAction()); | ||
}; | ||
el.style.touchAction = computeTouchAction(touchActions); | ||
// 绑定事件 | ||
this.unbinders = this._bindRecognizers(el); | ||
} else { | ||
el.style.touchAction = this.options.touchAction; | ||
} | ||
}; | ||
private _update() { | ||
this.setTouchAction(this.el); | ||
}; | ||
/** | ||
@@ -110,3 +131,3 @@ * 绑定手势到指定元素 | ||
return ['touchstart', 'touchmove', 'touchend', 'touchcancel'].map(eventName => { | ||
el.addEventListener(eventName, boundFn); | ||
el.addEventListener(eventName, boundFn, { passive: false }); | ||
return () => { | ||
@@ -148,10 +169,24 @@ el.removeEventListener(eventName, boundFn); | ||
get(name: string): any { | ||
return this.recognizers.find(recognizer => name === recognizer.name); | ||
return this.recognizers.find(recognizer => name === recognizer.options.name); | ||
}; | ||
set({ | ||
} = {}) { | ||
set(options: Options = DEFAULT_OPTIONS) { | ||
this.options = { ...DEFAULT_OPTIONS, ...options }; | ||
this._update(); | ||
}; | ||
handler(event: TouchEvent) { | ||
/** | ||
* 删除识别器 | ||
* @param {String} 识别器name | ||
*/ | ||
remove(recognizerName: string) { | ||
for (let [index, recognizer] of this.recognizers.entries()) { | ||
if (recognizerName === recognizer.options.name) { | ||
this.recognizers.splice(index, 1); | ||
break; | ||
} | ||
} | ||
}; | ||
public handler(event: TouchEvent) { | ||
// event.preventDefault(); | ||
@@ -164,5 +199,14 @@ // 记录各个阶段的input | ||
this.recognizers.forEach(recognizer => { | ||
recognizer.recognize(computed, (data: Computed) => { | ||
this.eventBus.dispatch(data.type, data); | ||
// 注入emit到recognizer中 | ||
recognizer.injectEmit(this.eventBus.emit.bind(this.eventBus)); | ||
// 构造原生event | ||
recognizer.afterEmit((type: string, payload: {[propName:string]:any}) => { | ||
if (this.options.domEvents) { | ||
let event:any = new Event(type, {}); | ||
event.computed = payload; | ||
this.el.dispatchEvent(event); | ||
} | ||
}); | ||
recognizer.recognize(computed); | ||
this.eventBus.emit('input', { ...computed, type: 'input' }); | ||
}); | ||
@@ -186,10 +230,6 @@ } | ||
*/ | ||
off(eventName: string, callback: EventHandler): void { | ||
this.eventBus.off(eventName, callback); | ||
off(eventName: string, handler: any = undefined): void { | ||
this.eventBus.off(eventName, handler); | ||
}; | ||
headUpperCase(str: string) { | ||
return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase()); | ||
}; | ||
/** | ||
@@ -196,0 +236,0 @@ * 销毁 |
@@ -6,5 +6,13 @@ /* | ||
* */ | ||
import { inputStatus, RecognizerStatus } from '../interface'; | ||
// import { RECOGNIZER_STATUS_POSSIBLE, RECOGNIZER_STATUS_BEGAN, RECOGNIZER_STATUS_CHANGED, RECOGNIZER_STATUS_ENDED, RECOGNIZER_STATUS_RECOGNIZED, RECOGNIZER_STATUS_CANCELLED, RECOGNIZER_STATUS_FAILED } from '../const' | ||
export default class Recognizer { | ||
import { Computed } from '../interface'; | ||
import {INPUT_CANCEL, INPUT_END, INPUT_MOVE, INPUT_START } from '../const'; | ||
import { | ||
STATUS_POSSIBLE, | ||
STATUS_START, | ||
STATUS_MOVE, | ||
STATUS_END, | ||
STATUS_CANCELLED, | ||
STATUS_FAILED, STATUS_RECOGNIZED | ||
} from '../const/recognizerStatus'; | ||
export default abstract class Recognizer { | ||
public name: string; | ||
@@ -15,9 +23,44 @@ public status: string; | ||
public requireFailureRecognizers: any[]; | ||
constructor(options: any) { | ||
this.name = options.name; | ||
this.status = 'unknown'; | ||
public update : ()=>void | ||
public afterEmitCallback: (type: string, payload: { [propName: string]: any })=>void; | ||
// 默认参数 | ||
public defaultOptions: { [propName: string]: any }; | ||
private _injectedEmit: any; | ||
constructor(options: any={}) { | ||
this.options = { ...this.defaultOptions, ...options }; | ||
this.status = STATUS_POSSIBLE; | ||
this.isRecognized = false; | ||
this.requireFailureRecognizers = []; | ||
this.update = ()=>{}; | ||
}; | ||
/** | ||
* 设置识别器 | ||
* @param {Object} 选项 | ||
*/ | ||
public set(options: {[propName: string]: any}){ | ||
this.options = { ...this.options, ...options }; | ||
this.update(); | ||
}; | ||
/** | ||
* 注入通用emit方法, 方便改写 | ||
*/ | ||
public injectEmit(emit: any) { | ||
this._injectedEmit = emit; | ||
}; | ||
public injectUpdate(fn:any){ | ||
this.update = fn; | ||
}; | ||
public emit(type: string, payload: { [propName: string]: any }) { | ||
payload.type = type; | ||
this._injectedEmit(type, payload); | ||
this.afterEmitCallback(type, payload); | ||
}; | ||
public afterEmit(callback:(type: string, payload: { [propName: string]: any })=>void){ | ||
this.afterEmitCallback = callback; | ||
}; | ||
/** | ||
@@ -40,3 +83,3 @@ * 前者需要后者识别失败才能触发 | ||
*/ | ||
public isOtherFailOrWait(): boolean { | ||
public isTheOtherFail(): boolean { | ||
const { length } = this.requireFailureRecognizers; | ||
@@ -46,3 +89,3 @@ for (let index = 0; index < length; index++) { | ||
// console.log(recognizer.status); | ||
if ('fail' !== recognizer.status && 'unknown' !== recognizer.status) { | ||
if (STATUS_FAILED !== recognizer.status && STATUS_POSSIBLE !== recognizer.status) { | ||
return false; | ||
@@ -57,26 +100,109 @@ } | ||
*/ | ||
public pointerLengthTest(pointerLength: number): boolean { | ||
public isValidPointerLength(pointerLength: number): boolean { | ||
return 0 === this.options.pointerLength || this.options.pointerLength === pointerLength | ||
}; | ||
/** | ||
* 识别手势事件的状态 | ||
* 手势的状态, 非原生事件的状态 | ||
* @param {inputStatus} 输入状态 | ||
* 是否只支持水平方向 | ||
*/ | ||
public getRecognizerState(inputStatus: inputStatus) { | ||
if (this.isRecognized) { | ||
if ('move' === inputStatus) { | ||
this.status = 'move'; | ||
} else if ('cancel' === inputStatus) { | ||
this.status = 'cancel'; | ||
} else { | ||
this.isRecognized = false; | ||
this.status = 'end'; | ||
public isOnlyHorizontal() { | ||
let isOnlyHorizontal = true; | ||
for (let direction of this.options.directions) { | ||
isOnlyHorizontal = -1 < ['left', 'right'].indexOf(direction); | ||
if (!isOnlyHorizontal) { | ||
return false; | ||
} | ||
} else { | ||
this.isRecognized = true; | ||
this.status = 'start'; | ||
} | ||
return this.status; | ||
return isOnlyHorizontal; | ||
}; | ||
/** | ||
* 是否只支持垂直方向 | ||
*/ | ||
public isOnlyVertical() { | ||
let isOnlyVertical = true; | ||
for (let direction of this.options.directions) { | ||
isOnlyVertical = -1 < ['up', 'down'].indexOf(direction); | ||
if (!isOnlyVertical) { | ||
return false; | ||
} | ||
} | ||
return isOnlyVertical; | ||
}; | ||
/** | ||
* 是否支持该方向 | ||
* @param {String} 方向 | ||
*/ | ||
public isVaildDirection(direction: string) { | ||
return -1 < this.options.directions.indexOf(direction); | ||
}; | ||
/** | ||
* 适用于大部分移动类型的手势 | ||
* @param {Computed} 计算数据 | ||
*/ | ||
recognize(computed: Computed) { | ||
// this.beforeRecognize(computed); | ||
let { inputStatus } = computed; | ||
// 是否识别成功 | ||
let isVaild = this.test(computed); | ||
// 如果识别结束, 那么重置状态 | ||
if (-1 < [STATUS_END, STATUS_CANCELLED, STATUS_FAILED, STATUS_RECOGNIZED].indexOf(this.status)) { | ||
this.status = STATUS_POSSIBLE; | ||
}; | ||
if (!this.isRecognized && !isVaild && STATUS_POSSIBLE === this.status && INPUT_END === inputStatus) { | ||
this.status = STATUS_FAILED; | ||
} else if (STATUS_POSSIBLE === this.status && INPUT_END === inputStatus && isVaild) { | ||
this.status = STATUS_RECOGNIZED; | ||
} else if (STATUS_POSSIBLE === this.status && isVaild) { | ||
this.status = STATUS_START; | ||
} else if (this.isRecognized && INPUT_MOVE === inputStatus) { | ||
this.status = STATUS_MOVE; | ||
} else if (this.isRecognized && INPUT_END === inputStatus) { | ||
this.status = STATUS_END; | ||
} else if (this.isRecognized && INPUT_CANCEL === inputStatus) { | ||
this.status = STATUS_CANCELLED; | ||
} | ||
// 是否已识别 | ||
this.isRecognized = -1 < [STATUS_START, STATUS_MOVE].indexOf(this.status); | ||
if (isVaild) { | ||
this.emit(this.options.name, computed); | ||
} | ||
// if (this.options.name == 'pinch') { | ||
// console.log({ | ||
// status: this.status, | ||
// scale:computed.scale, | ||
// isVaild, | ||
// isRecognized: this.isRecognized }); | ||
// } | ||
if (-1 < ['start', 'move', 'end', 'recognized'].indexOf(this.status)) { | ||
// panstart | panmove | panend | ||
this.emit(this.options.name + this.status, computed); | ||
this.afterRecognized(computed); | ||
} | ||
}; | ||
/** | ||
* 识别条件, 基于异步 | ||
* @param {Computed} 计算数据 | ||
* @param {(isRecognized: boolean) => void}} 接收是否识别状态 | ||
*/ | ||
abstract test(computed: Computed): boolean; | ||
/** | ||
* 识别成功后执行 | ||
* @param {Computed} 计算数据 | ||
*/ | ||
abstract afterRecognized(computed: Computed): void; | ||
/** | ||
* 计算当前手势的touch-action | ||
*/ | ||
abstract getTouchAction(): string[]; | ||
}; |
@@ -1,3 +0,5 @@ | ||
import { Computed, RecognizerCallback, directionString } from '../interface'; | ||
import Base from './Base'; | ||
import { Computed, directionString } from '../interface'; | ||
import {INPUT_MOVE} from '../const'; | ||
import Recognizer from './Base'; | ||
import getHV from '../untils/getHV'; | ||
interface Options { | ||
@@ -7,38 +9,26 @@ name?: string; | ||
pointerLength?: number; | ||
directions?: directionString[]; | ||
directions?: [directionString?, directionString?, directionString?, directionString?]; | ||
}; | ||
export default class PanRecognizer extends Base { | ||
export default class PanRecognizer extends Recognizer { | ||
public name: string; | ||
public options: Options; | ||
constructor({ | ||
name = 'pan', | ||
threshold = 10, | ||
pointerLength = 1, | ||
directions = ['up', 'right', 'down', 'left'] }: Options = {}) { | ||
super({ name }); | ||
this.name = name; | ||
this.options = { | ||
threshold, | ||
pointerLength, | ||
directions | ||
}; | ||
constructor(options: Options={}) { | ||
super(options); | ||
}; | ||
/** | ||
* 识别器 | ||
* @param {Computed} 计算数据 | ||
* @param {RecognizerCallback} 识别后触发钩子 | ||
*/ | ||
recognize(computed: Computed, callback: RecognizerCallback) { | ||
if (this.test(computed)) { | ||
// panleft | panright | pandown | panup | ||
callback({ ...computed, type: this.name + computed.direction }); | ||
// pan | ||
callback({ ...computed, type: this.name }); | ||
// panstart | panmove | panend | ||
let status = this.getRecognizerState(computed.inputStatus); | ||
callback({ ...computed, type: this.name + status }); | ||
getTouchAction() { | ||
let touchActions = ['auto']; | ||
let { hasHorizontal, hasVertical } = getHV(this.options.directions); | ||
if (hasHorizontal && hasVertical) { | ||
touchActions = ['none']; | ||
} else if (!hasHorizontal && hasVertical) { | ||
// 没有水平移动 | ||
touchActions = ['pan-x']; | ||
} else if (!hasVertical && hasHorizontal) { | ||
// 没有垂直移动 | ||
touchActions = ['pan-y']; | ||
} | ||
return touchActions; | ||
}; | ||
@@ -50,7 +40,24 @@ | ||
*/ | ||
test({ pointerLength, distance, direction}: Computed): Boolean { | ||
test({ distance, direction, inputStatus, pointerLength }: Computed): boolean { | ||
const isValidDirection = -1 !== this.options.directions.indexOf(direction); | ||
const isValidThreshold = this.options.threshold < distance; | ||
return isValidDirection && (this.isRecognized || isValidThreshold) && this.pointerLengthTest(pointerLength); | ||
return this.isValidPointerLength(pointerLength) && isValidDirection && | ||
(this.isRecognized || isValidThreshold) && INPUT_MOVE === inputStatus; | ||
}; | ||
/** | ||
* 识别后发布panleft等事件 | ||
* @param {Computed} 计算数据 | ||
*/ | ||
afterRecognized(computed: Computed) { | ||
this.emit(this.options.name + computed.direction, computed); | ||
} | ||
}; | ||
// 默认参数 | ||
PanRecognizer.prototype.defaultOptions = { | ||
name: 'pan', | ||
threshold: 10, | ||
pointerLength: 1, | ||
directions: ['up', 'right', 'down', 'left'] | ||
}; |
import { Computed } from '../interface'; | ||
import Base from './Base'; | ||
export default class PinchRecognizer extends Base { | ||
import Recognizer from './Base'; | ||
export default class PinchRecognizer extends Recognizer { | ||
private _prevScale: number; | ||
constructor(options: any) { | ||
constructor(options: any={}) { | ||
super(options); | ||
this._prevScale = 1; | ||
}; | ||
getTouchAction(){ | ||
return ['none']; | ||
}; | ||
recognize(computed: Computed, callback: (anyTouchEvent: any) => void) { | ||
if (this.test(computed)) { | ||
// console.log(computed); | ||
callback({ ...computed, type: 'pinch' }); | ||
// pinchstart | pinchmove | pinchend | ||
const type = this.getRecognizerState(computed.inputStatus); | ||
callback({ ...computed, type: 'pinch' + type }); | ||
// pinchin | pinchout | ||
const { scale } = computed; | ||
if (1 !== scale) { | ||
const inOrOut = scale > this._prevScale ? 'out' : 'in'; | ||
if ('move' === type) { | ||
callback({ ...computed, type: 'pinch' + inOrOut }); | ||
this._prevScale = scale; | ||
} | ||
} | ||
afterRecognized(computed: Computed) { | ||
// pinchin | pinchout | ||
const { scale } = computed; | ||
if (1 !== scale) { | ||
const inOrOut = scale > this._prevScale ? 'out' : 'in'; | ||
this.emit(this.options.name + inOrOut, computed); | ||
} | ||
this._prevScale = scale; | ||
}; | ||
test({ pointerLength, inputStatus }: Computed) { | ||
// 如果触碰点要大于1 | ||
// 如果已经识别, 并且当前事件是离开阶段 | ||
return 1 < pointerLength || ('end' === inputStatus && this.isRecognized); | ||
/** | ||
* 识别条件 | ||
* @param {Computed} 计算数据 | ||
* @param {(isRecognized: boolean) => void}} 接收是否识别状态 | ||
*/ | ||
test({ pointerLength, scale }: Computed): boolean { | ||
// 如果触碰点数要大于指定 | ||
// 如果缩放超过阈值, 或者已识别 | ||
return this.isValidPointerLength(pointerLength) && (this.options.threshold < Math.abs(scale - 1) || this.isRecognized); | ||
}; | ||
}; | ||
// 默认参数 | ||
PinchRecognizer.prototype.defaultOptions = { | ||
name: 'pinch', | ||
// 触发事件所需要的最小缩放比例 | ||
threshold: 0, | ||
pointerLength: 2, | ||
}; |
@@ -1,38 +0,62 @@ | ||
import { Computed, RecognizerCallback } from '../interface'; | ||
import Base from './Base'; | ||
export default class PressRecognizer extends Base { | ||
private timeoutId: number; | ||
constructor(options: any) { | ||
import { Computed } from '../interface'; | ||
import { | ||
STATUS_POSSIBLE, | ||
STATUS_CANCELLED, | ||
STATUS_FAILED, STATUS_RECOGNIZED | ||
} from '../const/recognizerStatus'; | ||
import { INPUT_CANCEL, INPUT_END, INPUT_MOVE, INPUT_START } from '../const'; | ||
import Recognizer from './Base'; | ||
export default class PressRecognizer extends Recognizer { | ||
protected _timeoutId: number; | ||
constructor(options: any = {}) { | ||
super(options); | ||
this.timeoutId = null; | ||
this._timeoutId = null; | ||
}; | ||
recognize(computed: Computed, callback: RecognizerCallback): void { | ||
const { inputStatus, distance, duration, maxPointerLength } = computed; | ||
if (1 < maxPointerLength) { | ||
this.cancel(); | ||
return; | ||
} else { | ||
if ('start' === inputStatus) { | ||
this.timeoutId = window.setTimeout(() => { | ||
callback({ ...computed, type: 'press' }); | ||
}, 250); | ||
} else if ('move' === inputStatus) { | ||
if (9 < distance) { | ||
this.cancel(); | ||
} | ||
} else if ('end' === inputStatus) { | ||
if (251 > duration || 9 < distance) { | ||
this.cancel(); | ||
} else { | ||
callback({ ...computed, type: 'pressup' }); | ||
} | ||
getTouchAction() { | ||
return ['auto']; | ||
}; | ||
recognize(computed: Computed): void { | ||
const { inputStatus, distance, duration } = computed; | ||
// 手指一直按住屏幕 | ||
if (this.test(computed)) { | ||
if (this.options.threshold < distance ) { | ||
this.cancel(); | ||
} else { | ||
this._timeoutId = window.setTimeout(() => { | ||
this.status = STATUS_RECOGNIZED; | ||
this.emit(this.options.name, computed); | ||
}, this.options.minPressTime); | ||
} | ||
} | ||
// 已识别, end阶段触发pressup | ||
else if (STATUS_RECOGNIZED === this.status && INPUT_END === inputStatus) { | ||
this.emit(`${this.options.name}up`, computed); | ||
// 复位状态 | ||
this.status = STATUS_POSSIBLE; | ||
} else { | ||
this.cancel(); | ||
this.status = STATUS_FAILED; | ||
} | ||
}; | ||
cancel() { | ||
clearTimeout(this.timeoutId); | ||
public test({ pointerLength }: Computed): boolean { | ||
return this.isValidPointerLength(pointerLength); | ||
}; | ||
protected cancel() { | ||
clearTimeout(this._timeoutId); | ||
this.status = STATUS_FAILED; | ||
} | ||
afterRecognized() { } | ||
}; | ||
// 默认参数 | ||
PressRecognizer.prototype.defaultOptions = { | ||
name: 'press', | ||
pointerLength: 1, | ||
threshold:9, | ||
minPressTime: 251 | ||
}; |
import Base from './Base'; | ||
import { Computed } from '../interface'; | ||
export default class RotateRecognizer extends Base { | ||
constructor(options: any) { | ||
constructor(options: any={}) { | ||
super(options); | ||
}; | ||
recognize(computed: any, callback: (paylod: any) => {}) { | ||
if (this.test(computed)) { | ||
callback({ ...computed, type: 'rotate', }); | ||
getTouchAction(){ | ||
return ['none']; | ||
}; | ||
/** | ||
* 无特殊事件要触发 | ||
*/ | ||
afterRecognized(computed: Computed) { }; | ||
//rotatestart |rotatemove |rotateend | ||
const type = this.getRecognizerState(computed.inputStatus); | ||
callback({ ...computed, type: 'rotate' + type }); | ||
} | ||
/** | ||
* 识别条件 | ||
* @param {Computed} 计算数据 | ||
* @return {Boolean} 接收是否识别状态 | ||
*/ | ||
test({ pointerLength, angle }: Computed): boolean { | ||
// 如果触碰点数要大于指定 | ||
// 如果缩放超过阈值, 或者已识别 | ||
return this.isValidPointerLength(pointerLength) && (this.options.threshold < Math.abs(angle) || this.isRecognized); | ||
}; | ||
}; | ||
test({ pointerLength }: Computed) { | ||
// 如果触碰点要大于1 | ||
// 如果已经识别, 并且当前事件是离开阶段 | ||
return 1 < pointerLength || this.isRecognized; | ||
}; | ||
// 默认参数 | ||
RotateRecognizer.prototype.defaultOptions = { | ||
name: 'rotate', | ||
// 触发事件所需要的最小角度 | ||
threshold: 0, | ||
pointerLength: 2, | ||
}; |
@@ -1,28 +0,55 @@ | ||
import Base from './Base'; | ||
export default class SwipeRecognizer extends Base { | ||
public name: string; | ||
constructor(options: any) { | ||
import Recognizer from './Base'; | ||
import { Computed } from '../interface'; | ||
import {INPUT_END} from '../const'; | ||
export default class SwipeRecognizer extends Recognizer { | ||
constructor(options: any={}) { | ||
super(options); | ||
this.name = 'swipe' | ||
}; | ||
recognize(computed: any, callback: (paylod: any) => {}): void { | ||
if (this.test(computed)) { | ||
callback({ ...computed, type: this.name }); | ||
getTouchAction(){ | ||
return ['none']; | ||
}; | ||
callback({ ...computed, type: this.name + computed.lastDirection }); | ||
/** | ||
* 识别后发布swipeleft等事件 | ||
* @param {Computed} 计算数据 | ||
*/ | ||
afterRecognized(computed: Computed) { | ||
this.emit(this.options.name + computed.lastDirection, computed); | ||
}; | ||
/** | ||
* 识别条件 | ||
* @param {Computed} 计算数据 | ||
*/ | ||
test(computed: Computed): boolean { | ||
const { inputStatus, lastDirection, direction, lastVelocityX, lastVelocityY, maxPointerLength, distance } = computed; | ||
// 如果只支持水平或垂直, 那么其他方向速率为0; | ||
// 有效速率 | ||
let vaildVelocityX: number = lastVelocityX; | ||
let vaildVelocityY: number = lastVelocityY; | ||
if (this.isOnlyHorizontal()) { | ||
vaildVelocityY = 0; | ||
} else if (this.isOnlyVertical()) { | ||
vaildVelocityX = 0; | ||
} | ||
}; | ||
test(computed: any): boolean { | ||
const { inputStatus, lastDirection, direction, lastVelocity, maxPointerLength, distance } = computed; | ||
let vaildVelocity = Math.sqrt(vaildVelocityX * vaildVelocityX + vaildVelocityY * vaildVelocityY) | ||
return 1 === maxPointerLength && | ||
10 < distance && | ||
'end' === inputStatus && | ||
'none' !== lastDirection && | ||
'none' !== direction && | ||
0.3 < lastVelocity; | ||
this.options.threshold < distance && | ||
INPUT_END === inputStatus && | ||
this.isVaildDirection(lastDirection) && | ||
this.isVaildDirection(lastDirection) && | ||
this.options.velocity < vaildVelocity; | ||
}; | ||
}; | ||
// 默认参数 | ||
SwipeRecognizer.prototype.defaultOptions = { | ||
name: 'swipe', | ||
threshold: 10, | ||
velocity: 0.3, | ||
pointerLength: 1, | ||
directions: ['up', 'right', 'down', 'left'] | ||
}; |
@@ -1,16 +0,21 @@ | ||
import { Computed, RecognizerCallback } from '../interface'; | ||
interface Options { name: string, pointer: number, taps: number }; | ||
import { Computed } from '../interface'; | ||
import { | ||
STATUS_RECOGNIZED, | ||
STATUS_POSSIBLE, | ||
STATUS_FAILED | ||
} from '../const/recognizerStatus'; | ||
interface Options { name?: string, pointer?: number, taps?: number, interval?: number }; | ||
const { setTimeout, clearTimeout } = window; | ||
import Base from './Base'; | ||
export default class TapRecognizer extends Base { | ||
import Recognizer from './Base'; | ||
import { INPUT_END } from '../const'; | ||
export default class TapRecognizer extends Recognizer { | ||
tapCount: number; | ||
tapTimeoutId: number; | ||
// 上一次tap的点击坐标 | ||
private _prevCenterX: number; | ||
private _prevCenterY: number; | ||
private _prevX: number; | ||
private _prevY: number; | ||
public options: Options; | ||
constructor(options: Options) { | ||
public defaultOptions: Options; | ||
constructor(options: Options={}) { | ||
super(options); | ||
this.options = options; | ||
this.tapTimeoutId = null; | ||
@@ -20,31 +25,40 @@ this.tapCount = 0; | ||
recognize(computed: Computed, callback: RecognizerCallback): void { | ||
getTouchAction() { | ||
return (1 < this.options.taps) ? ['manipulation'] : ['auto']; | ||
}; | ||
/** | ||
* 识别后执行 | ||
* @param {Computed} 计算数据 | ||
*/ | ||
public recognize(computed: Computed): void { | ||
this.status = STATUS_POSSIBLE; | ||
// this.cancel(); | ||
if (this.test(computed)) { | ||
// 累加点击 | ||
this.tapCount++; | ||
const isValidTapCount = this.options.taps === this.tapCount; | ||
if (this.hasRequireFailure()) { | ||
// 如果是需要其他手势失败才能触发的手势, | ||
// 需要等待(300ms)其他手势失败才能触发 | ||
clearTimeout(this.tapTimeoutId); | ||
this.cancel(); | ||
this.tapTimeoutId = setTimeout(() => { | ||
// console.log(this.isOtherFailOrWait()); | ||
if (this.options.taps === this.tapCount && this.isOtherFailOrWait()) { | ||
callback({ ...computed, tapCount: this.tapCount,type: this.options.name }); | ||
} | ||
if (isValidTapCount && this.isTheOtherFail()) { | ||
this.status = STATUS_RECOGNIZED; | ||
this.emit(this.options.name, { ...computed, tapCount: this.tapCount }); | ||
}; | ||
this.tapCount = 0; | ||
}, 300); | ||
}, this.options.interval); | ||
} else { | ||
// 如果不需要等待其他手势失败 | ||
// 那么立即执行 | ||
clearTimeout(this.tapTimeoutId); | ||
if (this.options.taps === this.tapCount) { | ||
callback({ ...computed, tapCount: this.tapCount, type: this.options.name }); | ||
this.cancel(); | ||
if (isValidTapCount) { | ||
this.emit(this.options.name, { ...computed, tapCount: this.tapCount }); | ||
this.tapCount = 0; | ||
} | ||
this.tapTimeoutId = setTimeout(() => { | ||
this.status = 'fail'; | ||
this.status = STATUS_FAILED; | ||
this.tapCount = 0; | ||
}, 300) | ||
}, this.options.interval) | ||
} | ||
@@ -54,19 +68,36 @@ } | ||
cancelTap() { | ||
/** | ||
* 取消等待识别的 | ||
*/ | ||
public cancel() { | ||
clearTimeout(this.tapTimeoutId); | ||
this.tapCount = 0; | ||
}; | ||
test({ inputStatus, distance, duration, maxPointerLength, centerX, centerY }: Computed): boolean { | ||
/** | ||
* 识别条件 | ||
* @param {Computed} 计算数据 | ||
* @return {Boolean} 是否验证成功 | ||
*/ | ||
public test(computed: Computed): boolean { | ||
const { abs, max } = Math; | ||
// 判断是否发生大的位置变化 | ||
this._prevCenterX = centerX; | ||
this._prevCenterY = centerY; | ||
let _xMove = abs(centerX - this._prevCenterX); | ||
let _yMove = abs(centerY - this._prevCenterY); | ||
const hasMove = 2 < max(_xMove, _yMove); | ||
if ('end' === inputStatus) { | ||
return 1 === maxPointerLength && 2 > distance && 250 > duration && !hasMove; | ||
} | ||
const { inputStatus, distance, duration, maxPointerLength, centerX, centerY } = computed; | ||
this._prevX = centerX; | ||
this._prevY = centerY; | ||
// 产生的位移 | ||
let offsetX = abs(centerX - this._prevX); | ||
let offsetY = abs(centerY - this._prevY); | ||
const hasMove = 2 < max(offsetX, offsetY); | ||
return INPUT_END === inputStatus && 1 === maxPointerLength && 2 > distance && 250 > duration && !hasMove | ||
}; | ||
afterRecognized(computed: Computed): void { } | ||
}; | ||
// 默认参数 | ||
TapRecognizer.prototype.defaultOptions = { | ||
name: 'tap', | ||
pointer: 1, | ||
taps: 1, | ||
interval: 300 | ||
}; |
@@ -1,30 +0,31 @@ | ||
import { dispatchTouchStart, dispatchTouchMove, dispatchTouchEnd } from './touchEventSmulate'; | ||
import TouchSimulator from './utils/TouchSimulator'; | ||
import panSimulator from './utils/Gesture/panSimulator'; | ||
import AnyTouch from '../src/main' | ||
document.body.innerHTML = '<div id="box">box</div>'; | ||
const el = document.getElementById('box'); | ||
const at = new AnyTouch(el); | ||
test('事件pan是否正确?', (done) => { | ||
let orgX = 0; | ||
let orgY = 0; | ||
at.on('panmove', ({ | ||
deltaX, | ||
deltaY | ||
}) => { | ||
orgX += deltaX; | ||
orgY += deltaY; | ||
}); | ||
let at = new AnyTouch(el); | ||
at.on('panend', (e) => { | ||
expect(orgX).toBe(100); | ||
expect(orgY).toBe(100); | ||
['up', 'right', 'down', 'left'].forEach(direction => { | ||
test('panup|panright|pandown|panleft是否正确?', (done) => { | ||
at.destroy(); | ||
at = new AnyTouch(el); | ||
const panRecognizer = at.get('pan'); | ||
panRecognizer.set({ directions: [direction] }); | ||
let dir = ''; | ||
at.on(`pan${direction}`, (ev) => { | ||
dir = ev.direction; | ||
}); | ||
at.on('panend', ev=>{ | ||
ev.preventDefault(); | ||
ev.stopImmediatePropagation(); | ||
ev.stopPropagation(); | ||
expect(dir).toBe(ev.direction); | ||
}); | ||
// 模拟事件 | ||
panSimulator(el, { direction }); | ||
done(); | ||
}); | ||
// 模拟touch触碰 | ||
dispatchTouchStart(el, { x: 0, y: 0 }); | ||
dispatchTouchMove(el, { x: 20, y: 20 }); | ||
dispatchTouchMove(el, { x: 120, y: 120 }); | ||
setTimeout(() => { | ||
dispatchTouchEnd(el); | ||
}, 100); | ||
}); | ||
}); |
@@ -1,9 +0,9 @@ | ||
import { dispatchTouchStart, dispatchTouchMove, dispatchTouchEnd } from './touchEventSmulate'; | ||
import TouchSimulator from './utils/TouchSimulator'; | ||
import sleep from './utils/sleep'; | ||
import AnyTouch from '../src/main' | ||
document.body.innerHTML = '<div id="box">box</div>'; | ||
const el = document.getElementById('box'); | ||
const at = new AnyTouch(el); | ||
test('事件tap是否正确?', (done) => { | ||
test('仅有tap识别, 事件是否触发', async (done) => { | ||
const at = new AnyTouch(el); | ||
let type = ''; | ||
@@ -15,8 +15,34 @@ at.on('tap', (e) => { | ||
}); | ||
const ts = new TouchSimulator(el); | ||
// 模拟touch触碰 | ||
ts.dispatchTouchStart([{ x: 0, y: 0 }]); | ||
await sleep(100); | ||
ts.dispatchTouchEnd(); | ||
}); | ||
test('tap与doubletap之间的requireFailure是否生效?', async (done) => { | ||
const tap2 = new AnyTouch.TapRecognizer({ name: 'doubletap', pointer: 1, taps: 2 }) | ||
const tap3 = new AnyTouch.TapRecognizer({ name: 'threetap', pointer: 1, taps: 3 }) | ||
const at = new AnyTouch(el); | ||
at.add(tap2); | ||
at.add(tap3); | ||
const tap1 = at.get('tap'); | ||
tap1.requireFailure(tap2); | ||
tap1.requireFailure(tap3); | ||
tap2.requireFailure(tap3); | ||
at.on('doubletap', (e) => { | ||
expect(e.type).toBe('doubletap'); | ||
done(); | ||
}); | ||
const ts = new TouchSimulator(el); | ||
// 模拟touch触碰 | ||
dispatchTouchStart(el, { x: 0, y: 0 }); | ||
setTimeout(() => { | ||
dispatchTouchEnd(el); | ||
}, 100); | ||
ts.dispatchTouchStart([{ x: 0, y: 0 }]); | ||
ts.dispatchTouchEnd(); | ||
await sleep(10); | ||
ts.dispatchTouchStart([{ x: 0, y: 0 }]); | ||
ts.dispatchTouchEnd(); | ||
}); |
{ | ||
"compilerOptions": { | ||
"esModuleInterop":true, | ||
"target": "es6", | ||
"target": "es3", | ||
"noImplicitAny": true, | ||
"removeComments": true, | ||
"preserveConstEnums": false, | ||
"sourceMap": false, | ||
"sourceMap": true, | ||
"outDir": "./esm", | ||
"lib": ["dom", "es2017"] | ||
"lib": ["dom", "es2017"], | ||
"downlevelIteration": true | ||
}, | ||
"include": [ | ||
"src/main.ts" | ||
"src/main.ts", "example/base/index.js" | ||
] | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
83003
58
2214
51
0
9
+ Addedmini-events@^0.0.2
+ Addedmini-events@0.0.2(transitive)
- RemovedsetTimeout@^0.4.9
- RemovedsetTimeout@0.4.9(transitive)