| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249 |
3x
3x
3x
3x
15x
15x
15x
15x
15x
15x
15x
15x
15x
15x
15x
3x
12x
3x
14x
14x
14x
14x
3x
28x
28x
28x
28x
3x
15x
15x
3x
1x
1x
3x
3x
3x
3x
6x
3x
12x
12x
12x
2x
10x
10x
10x
10x
10x
3x
576x
576x
576x
576x
96x
480x
480x
480x
334x
334x
480x
3x
12x
12x
12x
7x
5x
5x
5x
5x
14x
14x
14x
14x
14x
14x
14x
14x
14x
14x
14x
14x
14x
28x
28x
28x
28x
28x
28x
27x
28x
27x
28x
54x
28x
28x
480x
480x
3x
480x
3x
| /*
* Copyright (c) 2015 NAVER Corp.
* egjs projects are licensed under the MIT license
*/
import { $, isCssPropsFromAxes, setCssProps, revertCssProps } from "../utils";
import { ActiveEvent, ElementType, InputEventType } from "../types";
import { DIRECTION_ALL } from "../const";
import {
toAxis,
convertInputType,
InputType,
InputTypeObserver,
} from "./InputType";
export interface PinchInputOption {
scale?: number;
threshold?: number;
inputType?: string[];
touchAction?: string;
}
/**
* @typedef {Object} PinchInputOption The option object of the eg.Axes.PinchInput module
* @ko eg.Axes.PinchInput 모듈의 옵션 객체
* @param {Number} [scale=1] Coordinate scale that a user can move<ko>사용자의 동작으로 이동하는 좌표의 배율</ko>
* @param {Number} [threshold=0] Minimal scale before recognizing <ko>사용자의 Pinch 동작을 인식하기 위해산 최소한의 배율</ko>
* @param {String[]} [inputType=["touch", "pointer"]] Types of input devices
* - touch: Touch screen
* - pointer: Mouse and touch <ko>입력 장치 종류
* - touch: 터치 입력 장치
* - pointer: 마우스 및 터치</ko>
* @param {String} [touchAction="none"] Value that overrides the element's "touch-action" css property. It is set to "none" to prevent scrolling during touch. <ko>엘리먼트의 "touch-action" CSS 속성을 덮어쓰는 값. 터치 도중 스크롤을 방지하기 위해 "none" 으로 설정되어 있다.</ko>
**/
/**
* A module that passes the amount of change to eg.Axes when two pointers are moving toward (zoom-in) or away from each other (zoom-out). use one axis.
* @ko 2개의 pointer를 이용하여 zoom-in하거나 zoom-out 하는 동작의 변화량을 eg.Axes에 전달하는 모듈. 한 개 의 축을 사용한다.
* @example
* ```js
* const pinch = new eg.Axes.PinchInput("#area", {
* scale: 1
* });
*
* // Connect 'something' axis when two pointers are moving toward (zoom-in) or away from each other (zoom-out).
* axes.connect("something", pinch);
* ```
* @param {HTMLElement|String|jQuery} element An element to use the eg.Axes.PinchInput module <ko>eg.Axes.PinchInput 모듈을 사용할 엘리먼트</ko>
* @param {PinchInputOption} [options] The option object of the eg.Axes.PinchInput module<ko>eg.Axes.PinchInput 모듈의 옵션 객체</ko>
*/
export class PinchInput implements InputType {
public options: PinchInputOption;
public axes: string[] = [];
public element: HTMLElement = null;
private _observer: InputTypeObserver;
private _pinchFlag = false;
private _enabled = false;
private _originalCssProps: { [key: string]: string };
private _activeEvent: ActiveEvent = null;
private _baseValue: number;
private _isOverThreshold = false;
/**
*
*/
public constructor(el: ElementType, options?: PinchInputOption) {
this.element = $(el);
this.options = {
scale: 1,
threshold: 0,
inputType: ["touch", "pointer"],
touchAction: "none",
...options,
};
this._onPinchStart = this._onPinchStart.bind(this);
this._onPinchMove = this._onPinchMove.bind(this);
this._onPinchEnd = this._onPinchEnd.bind(this);
}
public mapAxes(axes: string[]) {
this.axes = axes;
}
public connect(observer: InputTypeObserver): InputType {
Iif (this._activeEvent) {
this._detachEvent();
}
this._attachEvent(observer);
this._originalCssProps = setCssProps(
this.element,
this.options,
DIRECTION_ALL
);
return this;
}
public disconnect() {
this._detachEvent();
Eif (!isCssPropsFromAxes(this._originalCssProps)) {
revertCssProps(this.element, this._originalCssProps);
}
return this;
}
/**
* Destroys elements, properties, and events used in a module.
* @ko 모듈에 사용한 엘리먼트와 속성, 이벤트를 해제한다.
*/
public destroy() {
this.disconnect();
this.element = null;
}
/**
* Enables input devices
* @ko 입력 장치를 사용할 수 있게 한다
* @return {PinchInput} An instance of a module itself <ko>모듈 자신의 인스턴스</ko>
*/
public enable() {
this._enabled = true;
return this;
}
/**
* Disables input devices
* @ko 입력 장치를 사용할 수 없게 한다.
* @return {PinchInput} An instance of a module itself <ko>모듈 자신의 인스턴스</ko>
*/
public disable() {
this._enabled = false;
return this;
}
/**
* Returns whether to use an input device
* @ko 입력 장치를 사용 여부를 반환한다.
* @return {Boolean} Whether to use an input device <ko>입력장치 사용여부</ko>
*/
public isEnabled() {
return this._enabled;
}
private _onPinchStart(event: InputEventType) {
const activeEvent = this._activeEvent;
const pinchEvent = activeEvent.onEventStart(event);
if (!pinchEvent || !this._enabled || activeEvent.getTouches(event) !== 2) {
return;
}
this._baseValue = this._observer.get(this)[this.axes[0]];
this._observer.hold(this, event);
this._pinchFlag = true;
this._isOverThreshold = false;
activeEvent.prevEvent = pinchEvent;
}
private _onPinchMove(event: InputEventType) {
const threshold = this.options.threshold;
const activeEvent = this._activeEvent;
const pinchEvent = activeEvent.onEventMove(event);
if (
!pinchEvent ||
!this._pinchFlag ||
!this._enabled ||
activeEvent.getTouches(event) !== 2
) {
return;
}
const distance = this._getDistance(pinchEvent.scale);
const offset = this._getOffset(
pinchEvent.scale,
activeEvent.prevEvent.scale
);
if (this._isOverThreshold || distance >= threshold) {
this._isOverThreshold = true;
this._observer.change(this, event, toAxis(this.axes, [offset]));
}
activeEvent.prevEvent = pinchEvent;
}
private _onPinchEnd(event: InputEventType) {
const activeEvent = this._activeEvent;
activeEvent.onEventEnd(event);
if (
!this._pinchFlag ||
!this._enabled ||
activeEvent.getTouches(event) >= 2
) {
return;
}
activeEvent.onRelease();
this._observer.release(this, event, [0], 0);
this._baseValue = null;
this._pinchFlag = false;
}
private _attachEvent(observer: InputTypeObserver) {
const activeEvent = convertInputType(this.options.inputType);
const element = this.element;
Iif (!activeEvent) {
return;
}
Iif (!element) {
throw new Error("Element to connect input does not exist.");
}
this._observer = observer;
this._enabled = true;
this._activeEvent = activeEvent;
activeEvent.start.forEach((event) => {
element.addEventListener(event, this._onPinchStart, false);
});
activeEvent.move.forEach((event) => {
element.addEventListener(event, this._onPinchMove, false);
});
activeEvent.end.forEach((event) => {
element.addEventListener(event, this._onPinchEnd, false);
});
}
private _detachEvent() {
const activeEvent = this._activeEvent;
const element = this.element;
Eif (element) {
activeEvent?.start.forEach((event) => {
element.removeEventListener(event, this._onPinchStart, false);
});
activeEvent?.move.forEach((event) => {
element.removeEventListener(event, this._onPinchMove, false);
});
activeEvent?.end.forEach((event) => {
element.removeEventListener(event, this._onPinchEnd, false);
});
}
this._enabled = false;
this._observer = null;
}
private _getOffset(pinchScale: number, Iprev: number = 1): number {
return this._baseValue * (pinchScale - prev) * this.options.scale;
}
private _getDistance(pinchScale: number): number {
return Math.abs(pinchScale - 1);
}
}
|