Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

any-touch

Package Overview
Dependencies
Maintainers
1
Versions
149
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

any-touch - npm Package Compare versions

Comparing version 0.0.2 to 0.0.3

.circleci/config.yml

6

jest.config.js

@@ -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 {

@@ -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"
]
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc