From eab854570363bf0a0bb5f213d95716282fbfc9de Mon Sep 17 00:00:00 2001 From: Seonghyun Ahn Date: Mon, 13 Jun 2022 14:06:40 +0900 Subject: [PATCH] feat: add useAnimation option to WheelInput (#189) * feat: add useAnimation option to WheelInput * fix: remove inappropriate inline functions * refactor: change access modifier and name of applyScale * skip: apply reviews * test: update manual test * chore: set default value of useAnimation to false * test: update mouse wheel test --- src/InputObserver.ts | 4 +- src/inputType/InputType.ts | 2 +- src/inputType/PanInput.ts | 38 +++++++------- src/inputType/WheelInput.ts | 11 +++- test/manual/js/wheel.js | 67 +++++++++++++++++++++++ test/manual/wheel.html | 73 ++++++++++++++++++++++++++ test/unit/inputType/PanInput.spec.js | 2 +- test/unit/inputType/PinchInput.spec.js | 2 +- test/unit/inputType/WheelInput.spec.js | 71 +++++++++++++++++++++++++ 9 files changed, 246 insertions(+), 24 deletions(-) create mode 100644 test/manual/js/wheel.js create mode 100644 test/manual/wheel.html diff --git a/src/InputObserver.ts b/src/InputObserver.ts index 4447f645..c03b701a 100644 --- a/src/InputObserver.ts +++ b/src/InputObserver.ts @@ -64,7 +64,7 @@ export class InputObserver implements InputTypeObserver { this._moveDistance = this._axisManager.get(input.axes); } - public change(input: InputType, event, offset: Axis, useDuration?: boolean) { + public change(input: InputType, event, offset: Axis, useAnimation?: boolean) { if ( this._isStopped || !this._interruptManager.isInterrupting() || @@ -108,7 +108,7 @@ export class InputObserver implements InputTypeObserver { input, event, }; - if (useDuration) { + if (useAnimation) { const duration = this._animationManager.getDuration(destPos, depaPos); this._animationManager.animateTo(destPos, duration, changeOption); } else { diff --git a/src/inputType/InputType.ts b/src/inputType/InputType.ts index 8367feca..ed32c057 100644 --- a/src/inputType/InputType.ts +++ b/src/inputType/InputType.ts @@ -25,7 +25,7 @@ export interface InputType { export interface InputTypeObserver { options: AxesOption; get(inputType: InputType): Axis; - change(inputType: InputType, event, offset: Axis, useDuration?: boolean); + change(inputType: InputType, event, offset: Axis, useAnimation?: boolean); hold(inputType: InputType, event); release( inputType: InputType, diff --git a/src/inputType/PanInput.ts b/src/inputType/PanInput.ts index d905deea..76d5a437 100644 --- a/src/inputType/PanInput.ts +++ b/src/inputType/PanInput.ts @@ -275,7 +275,7 @@ export class PanInput implements InputType { } } } - const offset: number[] = this._applyScale( + const offset: number[] = this._getOffset( [panEvent.offsetX, panEvent.offsetY], [ useDirection(DIRECTION_HORIZONTAL, this._direction, userDirection), @@ -306,7 +306,7 @@ export class PanInput implements InputType { this._detachWindowEvent(activeEvent); clearTimeout(this._rightEdgeTimer); const prevEvent = activeEvent.prevEvent; - const velocity = this._applyScale( + const velocity = this._getOffset( [ Math.abs(prevEvent.velocityX) * (prevEvent.offsetX < 0 ? -1 : 1), Math.abs(prevEvent.velocityY) * (prevEvent.offsetY < 0 ? -1 : 1), @@ -338,6 +338,19 @@ export class PanInput implements InputType { }); } + protected _getOffset(properties: number[], direction: boolean[]): number[] { + const offset: number[] = [0, 0]; + const scale = this.options.scale; + + if (direction[0]) { + offset[0] = properties[0] * scale[0]; + } + if (direction[1]) { + offset[1] = properties[1] * scale[1]; + } + return offset; + } + private _attachElementEvent(observer: InputTypeObserver) { const activeEvent = convertInputType(this.options.inputType); if (!activeEvent) { @@ -351,7 +364,7 @@ export class PanInput implements InputType { }); // adding event listener to element prevents invalid behavior in iOS Safari activeEvent.move.forEach((event) => { - this.element?.addEventListener(event, () => {}); + this.element?.addEventListener(event, this._voidFunction); }); } @@ -361,30 +374,19 @@ export class PanInput implements InputType { this.element?.removeEventListener(event, this._onPanstart); }); activeEvent?.move.forEach((event) => { - this.element?.removeEventListener(event, () => {}); + this.element?.removeEventListener(event, this._voidFunction); }); this._enabled = false; this._observer = null; } - private _applyScale(properties: number[], direction: boolean[]): number[] { - const offset: number[] = [0, 0]; - const scale = this.options.scale; - - if (direction[0]) { - offset[0] = properties[0] * scale[0]; - } - if (direction[1]) { - offset[1] = properties[1] * scale[1]; - } - return offset; - } - private _forceRelease = () => { const activeEvent = this._activeEvent; const prevEvent = activeEvent.prevEvent; - this._detachWindowEvent(activeEvent); activeEvent.onRelease(); this._observer.release(this, prevEvent, [0, 0]); + this._detachWindowEvent(activeEvent); }; + + private _voidFunction = () => {}; } diff --git a/src/inputType/WheelInput.ts b/src/inputType/WheelInput.ts index e0599018..7e208bee 100644 --- a/src/inputType/WheelInput.ts +++ b/src/inputType/WheelInput.ts @@ -6,6 +6,7 @@ export interface WheelInputOption { scale?: number; releaseDelay?: number; useNormalized?: boolean; + useAnimation?: boolean; } /** @@ -13,6 +14,8 @@ export interface WheelInputOption { * @ko eg.Axes.WheelInput 모듈의 옵션 객체 * @param {Number} [scale=1] Coordinate scale that a user can move사용자의 동작으로 이동하는 좌표의 배율 * @param {Number} [releaseDelay=300] Millisecond that trigger release event after last input마지막 입력 이후 release 이벤트가 트리거되기까지의 밀리초 + * @param {Boolean} [useNormalized=true] Whether to calculate scroll speed the same in all browsers모든 브라우저에서 스크롤 속도를 동일하게 처리할지 여부 + * @param {Boolean} [useAnimation=false] Whether to process coordinate changes through the mouse wheel as a continuous animation마우스 휠을 통한 좌표 변화를 연속적인 애니메이션으로 처리할지 여부 **/ /** @@ -50,6 +53,7 @@ export class WheelInput implements InputType { scale: 1, releaseDelay: 300, useNormalized: true, + useAnimation: false, }, ...options, }; @@ -127,7 +131,12 @@ export class WheelInput implements InputType { (event.deltaY > 0 ? -1 : 1) * this.options.scale * (this.options.useNormalized ? 1 : Math.abs(event.deltaY)); - this._observer.change(this, event, toAxis(this.axes, [offset]), true); + this._observer.change( + this, + event, + toAxis(this.axes, [offset]), + this.options.useAnimation + ); clearTimeout(this._timer); this._timer = setTimeout(() => { diff --git a/test/manual/js/wheel.js b/test/manual/js/wheel.js new file mode 100644 index 00000000..26675ecb --- /dev/null +++ b/test/manual/js/wheel.js @@ -0,0 +1,67 @@ +const wrapper = document.getElementById("content"); +const ui0 = document.getElementById("ui0"); +const ui1 = document.getElementById("ui1"); + +const axes0 = new eg.Axes( + { + zoom: { + range: [1, 4], + }, + }, + { + deceleration: 0.00002, + } +); + +axes0.on({ + animationStart: (e) => { + if (e && e.inputEvent) { + e.inputEvent.__childrenAxesAlreadyChanged = false; + } + }, + change: (e) => { + var pos = e.pos; + + ui0.style[ + eg.Axes.TRANSFORM + ] = `translate3d(200px, 200px, 0) scale(${pos.zoom})`; + ui0.innerHTML = `${pos.zoom.toFixed(2)}`; + }, +}); + +axes0.connect( + "zoom", + new eg.Axes.WheelInput(wrapper, { + scale: 1 / 1000, + useNormalized: false, + useAnimation: true, + }) +); +axes0.setTo({ zoom: 3 }); + +const axes1 = new eg.Axes({ + zoom: { + range: [1, 4], + }, +}); + +axes1.on({ + change: (e) => { + var pos = e.pos; + + ui1.style[ + eg.Axes.TRANSFORM + ] = `translate3d(200px, 200px, 0) scale(${pos.zoom})`; + ui1.innerHTML = `${pos.zoom.toFixed(2)}`; + }, +}); + +axes1.connect( + "zoom", + new eg.Axes.WheelInput(wrapper, { + scale: 1 / 1000, + useNormalized: false, + useAnimation: false, + }) +); +axes1.setTo({ zoom: 3 }); diff --git a/test/manual/wheel.html b/test/manual/wheel.html new file mode 100644 index 00000000..52aa3d0e --- /dev/null +++ b/test/manual/wheel.html @@ -0,0 +1,73 @@ + + + + + egjs-axes + + + + + +

Pan Input

+
+
+

UseAnimation: true

+
+
+
+ 3.0 +
+
+
+
+
+

UseAnimation: false

+
+
+
+ 3.0 +
+
+
+
+
+ + + + + diff --git a/test/unit/inputType/PanInput.spec.js b/test/unit/inputType/PanInput.spec.js index 422cc58c..e8e9a89b 100644 --- a/test/unit/inputType/PanInput.spec.js +++ b/test/unit/inputType/PanInput.spec.js @@ -314,7 +314,7 @@ describe("PanInput", () => { }); }); - describe("options test", () => { + describe("Options", () => { beforeEach(() => { el = sandbox(); inst = new Axes({ diff --git a/test/unit/inputType/PinchInput.spec.js b/test/unit/inputType/PinchInput.spec.js index 82fa1b02..a393edde 100644 --- a/test/unit/inputType/PinchInput.spec.js +++ b/test/unit/inputType/PinchInput.spec.js @@ -217,7 +217,7 @@ describe("PinchInput", () => { }); }); - describe("options test", () => { + describe("Options", () => { beforeEach(() => { el = sandbox(); inst = new Axes({ diff --git a/test/unit/inputType/WheelInput.spec.js b/test/unit/inputType/WheelInput.spec.js index 5a64adbf..69be66c3 100644 --- a/test/unit/inputType/WheelInput.spec.js +++ b/test/unit/inputType/WheelInput.spec.js @@ -274,4 +274,75 @@ describe("WheelInput", () => { }); }); }); + + describe("Options", () => { + beforeEach(() => { + el = sandbox(); + inst = new Axes({ + x: { + range: [0, 200], + }, + }); + }); + afterEach(() => { + if (inst) { + inst.destroy(); + inst = null; + } + if (input) { + input.destroy(); + input = null; + } + cleanup(); + }); + describe("useAnimation", () => { + let animationStartHandler; + let animationEndHandler; + beforeEach(() => { + animationStartHandler = sinon.spy(); + animationEndHandler = sinon.spy(); + inst.on({ + animationStart: animationStartHandler, + animationEnd: animationEndHandler, + }); + }); + + it("should change coordinate smoothly by animation when useAnimation is true", (done) => { + // Given + const deltaY = 1; + input = new WheelInput(el, { scale: -10, useAnimation: true }); + inst.connect(["x"], input); + + // When + TestHelper.wheelVertical(el, deltaY, () => { + // Then + expect(inst.axisManager.get().x).to.be.not.equal(10); + setTimeout(() => { + expect(animationStartHandler.calledOnce).to.be.true; + expect(animationEndHandler.calledOnce).to.be.true; + expect(inst.axisManager.get().x).to.be.equal(10); + done(); + }, 200); + }); + }); + + it("should change coordinate immediately when useAnimation is false", (done) => { + // Given + const deltaY = 1; + input = new WheelInput(el, { scale: -10, useAnimation: false }); + inst.connect(["x"], input); + + // When + TestHelper.wheelVertical(el, deltaY, () => { + // Then + expect(inst.axisManager.get().x).to.be.equal(10); + setTimeout(() => { + expect(animationStartHandler.called).to.be.false; + expect(animationEndHandler.called).to.be.false; + done(); + }, 200); + }); + }); + }); + }); });