• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

naver / egjs-flicking / 10557177632

26 Aug 2024 09:22AM UTC coverage: 38.327% (-44.5%) from 82.855%
10557177632

Pull #886

github

daybrush
fix: recalculate camera offset
Pull Request #886: fix: recalculate camera offset

2039 of 7372 branches covered (27.66%)

Branch coverage included in aggregate %.

11 of 29 new or added lines in 2 files covered. (37.93%)

5575 existing lines in 46 files now uncovered.

5099 of 11252 relevant lines covered (45.32%)

10.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

55.53
/src/control/AxesController.ts
1
/*
1✔
2
 * Copyright (c) 2015 NAVER Corp.
1✔
3
 * egjs projects are licensed under the MIT license
×
4
 */
5
import Axes, { PanInput, AxesEvents, OnRelease } from "@egjs/axes";
5✔
6

×
7
import Flicking from "../Flicking";
8
import FlickingError from "../core/FlickingError";
5✔
9
import * as AXES from "../const/axes";
5✔
10
import * as ERROR from "../const/error";
5✔
11
import { ORDER } from "../const/external";
5✔
12
import { getFlickingAttached, parseBounce } from "../utils";
5✔
13
import { ControlParams } from "../type/external";
1!
14

18!
15
import StateMachine from "./StateMachine";
23✔
16

17
/**
×
18
 * A controller that handles the {@link https://naver.github.io/egjs-axes/ @egjs/axes} events
19
 * @ko {@link https://naver.github.io/egjs-axes/ @egjs/axes}의 이벀트λ₯Ό μ²˜λ¦¬ν•˜λŠ” 컨트둀러 μ»΄ν¬λ„ŒνŠΈ
20
 * @internal
1!
21
 */
1✔
22
class AxesController {
5✔
23
  private _flicking: Flicking | null;
24
  private _axes: Axes | null;
25
  private _panInput: PanInput | null;
1✔
26
  private _stateMachine: StateMachine;
3✔
27

1✔
28
  private _animatingContext: { start: number; end: number; offset: number };
20!
29
  private _dragged: boolean;
1✔
30

1✔
31
  /**
32
   * An {@link https://naver.github.io/egjs-axes/docs/api/Axes Axes} instance
1✔
33
   * @ko {@link https://naver.github.io/egjs-axes/docs/api/Axes Axes}의 μΈμŠ€ν„΄μŠ€
2!
34
   * @type {Axes | null}
35
   * @see https://naver.github.io/egjs-axes/docs/api/Axes
1✔
36
   * @readonly
37
   */
38
  public get axes() { return this._axes; }
5✔
39
  /**
40
   * An {@link https://naver.github.io/egjs-axes/docs/api/PanInput PanInput} instance
1✔
41
   * @ko {@link https://naver.github.io/egjs-axes/docs/api/PanInput PanInput}의 μΈμŠ€ν„΄μŠ€
1✔
42
   * @type {PanInput | null}
1✔
43
   * @see https://naver.github.io/egjs-axes/docs/api/PanInput
1✔
44
   * @readonly
1✔
45
   */
1✔
46
  public get panInput() { return this._panInput; }
6✔
47
  /**
48
   * @internal
49
   */
50
  public get stateMachine() { return this._stateMachine; }
5✔
51
  /**
52
   * A activated {@link State} that shows the current status of the user input or the animation
1✔
53
   * @ko ν˜„μž¬ ν™œμ„±ν™”λœ {@link State} μΈμŠ€ν„΄μŠ€λ‘œ μ‚¬μš©μž μž…λ ₯ λ˜λŠ” μ• λ‹ˆλ©”μ΄μ…˜ μƒνƒœλ₯Ό λ‚˜νƒ€λƒ…λ‹ˆλ‹€
54
   * @type {State}
55
   */
1✔
56
  public get state() { return this._stateMachine.state; }
26✔
57
  /**
58
   * A context of the current animation playing
59
   * @ko ν˜„μž¬ μž¬μƒμ€‘μΈ μ• λ‹ˆλ©”μ΄μ…˜ 정보
1✔
60
   * @type {object}
61
   * @property {number} start A start position of the animation<ko>μ• λ‹ˆλ©”μ΄μ…˜ μ‹œμž‘ 지점</ko>
×
62
   * @property {number} end A end position of the animation<ko>μ• λ‹ˆλ©”μ΄μ…˜ 끝 지점</ko>
63
   * @property {number} offset camera offset<ko>카메라 μ˜€ν”„μ…‹</ko>
1✔
64
   * @readonly
×
65
   */
66
  public get animatingContext() { return this._animatingContext; }
5✔
67
  /**
68
   * A current control parameters of the Axes instance
69
   * @ko ν™œμ„±ν™”λœ ν˜„μž¬ Axes νŒ¨λŸ¬λ―Έν„°λ“€
70
   * @type {ControlParams}
1✔
71
   */
1✔
72
  public get controlParams(): ControlParams {
5✔
73
    const axes = this._axes;
1✔
74

75
    if (!axes) {
×
76
      return {
77
        range: { min: 0, max: 0 },
78
        position: 0,
79
        circular: false
80
      };
81
    }
82

83
    const axis = axes.axis[AXES.POSITION_KEY];
84

85
    return {
1✔
86
      range: { min: axis.range![0], max: axis.range![1] },
87
      circular: (axis.circular as boolean[])[0],
88
      position: this.position
89
    };
90
  }
91

92
  /**
93
   * A Boolean indicating whether the user input is enabled
94
   * @ko ν˜„μž¬ μ‚¬μš©μž μž…λ ₯이 ν™œμ„±ν™”λ˜μ—ˆλŠ”μ§€λ₯Ό λ‚˜νƒ€λ‚΄λŠ” κ°’
95
   * @type {boolean}
96
   * @readonly
97
   */
1✔
98
  public get enabled() { return this._panInput?.isEnabled() ?? false; }
5!
99
  /**
100
   * Current position value in {@link https://naver.github.io/egjs-axes/release/latest/doc/eg.Axes.html Axes} instance
101
   * @ko {@link https://naver.github.io/egjs-axes/release/latest/doc/eg.Axes.html Axes} μΈμŠ€ν„΄μŠ€ λ‚΄λΆ€μ˜ ν˜„μž¬ μ’Œν‘œ κ°’
102
   * @type {number}
103
   * @readonly
104
   */
105
  public get position() { return this._axes?.get([AXES.POSITION_KEY])[AXES.POSITION_KEY] ?? 0; }
6!
106
  /**
107
   * Current range value in {@link https://naver.github.io/egjs-axes/release/latest/doc/eg.Axes.html Axes} instance
108
   * @ko {@link https://naver.github.io/egjs-axes/release/latest/doc/eg.Axes.html Axes} μΈμŠ€ν„΄μŠ€ λ‚΄λΆ€μ˜ ν˜„μž¬ 이동 λ²”μœ„ κ°’
109
   * @type {number[]}
110
   * @readonly
111
   */
13✔
112
  public get range() { return this._axes?.axis[AXES.POSITION_KEY].range ?? [0, 0]; }
5!
113
  /**
114
   * Actual bounce size(px)
115
   * @ko 적용된 bounce 크기(px λ‹¨μœ„)
1✔
116
   * @type {number[]}
117
   * @readonly
118
   */
119
  public get bounce() { return this._axes?.axis[AXES.POSITION_KEY].bounce as number[] | undefined; }
5!
120

121
  /** */
122
  public constructor() {
14✔
123
    this._resetInternalValues();
14✔
124
    this._stateMachine = new StateMachine();
14✔
125
  }
126

127
  /**
128
   * Initialize AxesController
129
   * @ko AxesControllerλ₯Ό μ΄ˆκΈ°ν™”ν•©λ‹ˆλ‹€
1✔
130
   * @param {Flicking} flicking An instance of Flicking
131
   * @chainable
132
   * @return {this}
133
   */
134
  public init(flicking: Flicking): this {
14✔
135
    this._flicking = flicking;
14✔
136

137
    this._axes = new Axes({
14!
138
      [AXES.POSITION_KEY]: {
139
        range: [0, 0],
140
        circular: false,
141
        bounce: [0, 0]
142
      }
143
    }, {
144
      deceleration: flicking.deceleration,
145
      interruptable: flicking.interruptable,
146
      nested: flicking.nested,
147
      easing: flicking.easing
148
    });
149
    this._panInput = new PanInput(flicking.viewport.element, {
14✔
150
      inputType: flicking.inputType,
151
      threshold: flicking.dragThreshold,
152
      iOSEdgeSwipeThreshold: flicking.iOSEdgeSwipeThreshold,
153
      preventDefaultOnDrag: flicking.preventDefaultOnDrag,
154
      scale: flicking.horizontal ? [flicking.camera.panelOrder === ORDER.RTL ? 1 : -1, 0] : [0, -1],
1!
155
      releaseOnScroll: true
156
    });
157

158
    const axes = this._axes;
14✔
159

160
    axes.connect(flicking.horizontal ? [AXES.POSITION_KEY, ""] : ["", AXES.POSITION_KEY], this._panInput);
14!
161

×
162
    for (const key in AXES.EVENT) {
70✔
163
      const eventType = AXES.EVENT[key] as keyof AxesEvents;
70✔
164

165
      axes.on(eventType, (e: AxesEvents[typeof eventType]) => {
71✔
166
        this._stateMachine.fire(eventType, {
330✔
167
          flicking,
168
          axesEvent: e
169
        });
170
      });
171
    }
172

×
173
    return this;
14✔
174
  }
175

176
  /**
1✔
177
   * Destroy AxesController and return to initial state
178
   * @ko AxesControllerλ₯Ό 초기 μƒνƒœλ‘œ λ˜λŒλ¦½λ‹ˆλ‹€
179
   * @return {void}
180
   */
181
  public destroy(): void {
5✔
182
    if (this._axes) {
×
UNCOV
183
      this.removePreventClickHandler();
×
184
      this._axes.destroy();
185
    }
186

187
    this._panInput?.destroy();
1!
188

189
    this._resetInternalValues();
190
  }
191

192
  /**
193
   * Enable input from the user (mouse/touch)
194
   * @ko μ‚¬μš©μžμ˜ μž…λ ₯(마우슀/ν„°μΉ˜)λ₯Ό ν™œμ„±ν™”ν•©λ‹ˆλ‹€
×
195
   * @chainable
196
   * @return {this}
197
   */
198
  public enable(): this {
5✔
199
    this._panInput?.enable();
×
200

201
    return this;
202
  }
203

204
  /**
205
   * Disable input from the user (mouse/touch)
1✔
206
   * @ko μ‚¬μš©μžμ˜ μž…λ ₯(마우슀/ν„°μΉ˜)λ₯Ό λ§‰μŠ΅λ‹ˆλ‹€
207
   * @chainable
1✔
208
   * @return {this}
1✔
209
   */
1✔
210
  public disable(): this {
5✔
211
    this._panInput?.disable();
×
212

213
    return this;
214
  }
215

216
  /**
217
   * Releases ongoing user input (mouse/touch)
218
   * @ko μ‚¬μš©μžμ˜ ν˜„μž¬ μž…λ ₯(마우슀/ν„°μΉ˜)λ₯Ό μ€‘λ‹¨μ‹œν‚΅λ‹ˆλ‹€
219
   * @chainable
220
   * @return {this}
221
   */
1✔
222
  public release(): this {
5✔
223
    this._panInput?.release();
×
224

225
    return this;
226
  }
1!
227

228
  /**
229
   * Change the destination and duration of the animation currently playing
1✔
230
   * @ko μž¬μƒ 쀑인 μ• λ‹ˆλ©”μ΄μ…˜μ˜ λͺ©μ μ§€μ™€ μž¬μƒ μ‹œκ°„μ„ λ³€κ²½ν•©λ‹ˆλ‹€
1!
231
   * @param {number} position A position to move<ko>이동할 μ’Œν‘œ</ko>
1✔
232
   * @param {number} duration Duration of the animation (unit: ms)<ko>μ• λ‹ˆλ©”μ΄μ…˜ μ§„ν–‰ μ‹œκ°„ (λ‹¨μœ„: ms)</ko>
5✔
233
   * @chainable
5✔
234
   * @return {this}
235
   */
236
  public updateAnimation(position: number, duration?: number): this {
5✔
237
    this._animatingContext = {
238
      ...this._animatingContext,
239
      end: position
240
    };
1✔
241
    this._axes?.updateAnimation({
5!
242
      destPos: { [AXES.POSITION_KEY]: position },
243
      duration
1✔
244
    });
245

246
    return this;
247
  }
248

249
  /**
250
   * Stops the animation currently playing
1✔
251
   * @ko μž¬μƒ 쀑인 μ• λ‹ˆλ©”μ΄μ…˜μ„ μ€‘λ‹¨μ‹œν‚΅λ‹ˆλ‹€
252
   * @chainable
1!
253
   * @return {this}
1✔
254
   */
1✔
255
  public stopAnimation(): this {
5✔
256
    this._axes?.stopAnimation();
1!
257

1✔
258
    return this;
259
  }
260

261
  /**
262
   * Update {@link https://naver.github.io/egjs-axes/ @egjs/axes}'s state
263
   * @ko {@link https://naver.github.io/egjs-axes/ @egjs/axes}의 μƒνƒœλ₯Ό κ°±μ‹ ν•©λ‹ˆλ‹€
264
   * @chainable
265
   * @throws {FlickingError}
1✔
266
   * {@link ERROR_CODE NOT_ATTACHED_TO_FLICKING} When {@link AxesController#init init} is not called before
267
   * <ko>{@link AxesController#init init}이 이전에 ν˜ΈμΆœλ˜μ§€ μ•Šμ€ 경우</ko>
×
268
   * @return {this}
269
   */
270
  public update(controlParams: ControlParams): this {
5✔
271
    const flicking = getFlickingAttached(this._flicking);
28✔
272
    const camera = flicking.camera;
28✔
273
    const axes = this._axes!;
28✔
274
    const axis = axes.axis[AXES.POSITION_KEY];
28✔
275

276
    axis.circular = [controlParams.circular, controlParams.circular];
29✔
277
    axis.range = [controlParams.range.min, controlParams.range.max];
28✔
278
    axis.bounce = parseBounce(flicking.bounce, camera.size);
28!
279

280
    axes.axisManager.set({ [AXES.POSITION_KEY]: controlParams.position });
28✔
281

282
    return this;
28✔
283
  }
284

285
  /**
286
   * Attach a handler to the camera element to prevent click events during animation
287
   * @ko 카메라 μ—˜λ¦¬λ¨ΌνŠΈμ— μ• λ‹ˆλ©”μ΄μ…˜ 도쀑에 클릭 이벀트λ₯Ό λ°©μ§€ν•˜λŠ” ν•Έλ“€λŸ¬λ₯Ό λΆ€μ°©ν•©λ‹ˆλ‹€
1✔
288
   * @return {this}
289
   */
×
290
  public addPreventClickHandler(): this {
5✔
291
    const flicking = getFlickingAttached(this._flicking);
14✔
292
    const axes = this._axes!;
14✔
293
    const cameraEl = flicking.camera.element;
14✔
294

295
    axes.on(AXES.EVENT.HOLD, this._onAxesHold);
14✔
296
    axes.on(AXES.EVENT.CHANGE, this._onAxesChange);
14✔
297
    cameraEl.addEventListener("click", this._preventClickWhenDragged, true);
14✔
298

299
    return this;
14✔
300
  }
1✔
301

302
  /**
303
   * Detach a handler to the camera element to prevent click events during animation
304
   * @ko 카메라 μ—˜λ¦¬λ¨ΌνŠΈμ— μ• λ‹ˆλ©”μ΄μ…˜ 도쀑에 클릭 이벀트λ₯Ό λ°©μ§€ν•˜λŠ” ν•Έλ“€λŸ¬λ₯Ό νƒˆμ°©ν•©λ‹ˆλ‹€
×
305
   * @return {this}
306
   */
307
  public removePreventClickHandler(): this {
5✔
UNCOV
308
    const flicking = getFlickingAttached(this._flicking);
×
309
    const axes = this._axes!;
310
    const cameraEl = flicking.camera.element;
311

312
    axes.off(AXES.EVENT.HOLD, this._onAxesHold);
313
    axes.off(AXES.EVENT.CHANGE, this._onAxesChange);
314
    cameraEl.removeEventListener("click", this._preventClickWhenDragged, true);
315

316
    return this;
1✔
317
  }
318

×
319
  /**
320
   * Run Axes's {@link https://naver.github.io/egjs-axes/release/latest/doc/eg.Axes.html#setTo setTo} using the given position
321
   * @ko Axes의 {@link https://naver.github.io/egjs-axes/release/latest/doc/eg.Axes.html#setTo setTo} λ©”μ†Œλ“œλ₯Ό μ£Όμ–΄μ§„ μ’Œν‘œλ₯Ό μ΄μš©ν•˜μ—¬ μˆ˜ν–‰ν•©λ‹ˆλ‹€
322
   * @param {number} position A position to move<ko>이동할 μ’Œν‘œ</ko>
323
   * @param {number} duration Duration of the animation (unit: ms)<ko>μ• λ‹ˆλ©”μ΄μ…˜ μ§„ν–‰ μ‹œκ°„ (λ‹¨μœ„: ms)</ko>
324
   * @param {number} [axesEvent] If provided, it'll use its {@link https://naver#github#io/egjs-axes/release/latest/doc/eg#Axes#html#setTo setTo} method instead<ko>이 값이 μ£Όμ–΄μ‘Œμ„ 경우, ν•΄λ‹Ή 이벀트의 {@link https://naver#github#io/egjs-axes/release/latest/doc/eg#Axes#html#setTo setTo} λ©”μ†Œλ“œλ₯Ό λŒ€μ‹ ν•΄μ„œ μ‚¬μš©ν•©λ‹ˆλ‹€.</ko>
325
   * @throws {FlickingError}
326
   * |code|condition|
327
   * |---|---|
328
   * |{@link ERROR_CODE NOT_ATTACHED_TO_FLICKING}|When {@link Control#init init} is not called before|
329
   * |{@link ERROR_CODE ANIMATION_INTERRUPTED}|When the animation is interrupted by user input|
330
   * <ko>
1✔
331
   *
332
   * |code|condition|
3✔
333
   * |---|---|
3✔
334
   * |{@link ERROR_CODE NOT_ATTACHED_TO_FLICKING}|{@link Control#init init}이 이전에 ν˜ΈμΆœλ˜μ§€ μ•Šμ€ 경우|
3✔
335
   * |{@link ERROR_CODE ANIMATION_INTERRUPTED}|μ‚¬μš©μž μž…λ ₯에 μ˜ν•΄ μ• λ‹ˆλ©”μ΄μ…˜μ΄ μ€‘λ‹¨λœ 경우|
3✔
336
   *
3✔
337
   * </ko>
3✔
338
   * @return {Promise<void>} A Promise which will be resolved after reaching the target position<ko>ν•΄λ‹Ή μ’Œν‘œ λ„λ‹¬μ‹œμ— resolveλ˜λŠ” Promise</ko>
3✔
339
   */
3✔
340
  public animateTo(position: number, duration: number, axesEvent?: OnRelease): Promise<void> {
8✔
341
    const axes = this._axes;
4✔
342
    const state = this._stateMachine.state;
4✔
343

344
    if (!axes) {
4!
345
      return Promise.reject(new FlickingError(ERROR.MESSAGE.NOT_ATTACHED_TO_FLICKING, ERROR.CODE.NOT_ATTACHED_TO_FLICKING));
346
    }
347

1✔
348
    const startPos = axes.get([AXES.POSITION_KEY])[AXES.POSITION_KEY];
5✔
349

1✔
350
    if (startPos === position) {
5!
351
      const flicking = getFlickingAttached(this._flicking);
1✔
352

1✔
353
      flicking.camera.lookAt(position);
1✔
354

1✔
355
      if (state.targetPanel) {
×
356
        flicking.control.setActive(state.targetPanel, flicking.control.activePanel, axesEvent?.isTrusted ?? false);
×
357
      }
358
      return Promise.resolve();
359
    }
360

361
    this._animatingContext = {
5✔
362
      start: startPos,
1✔
363
      end: position,
1✔
364
      offset: 0
1✔
365
    };
1✔
366

1✔
367
    const animate = () => {
5✔
368
      const resetContext = () => {
5✔
369
        this._animatingContext = { start: 0, end: 0, offset: 0 };
2✔
370
      };
371

372
      axes.once(AXES.EVENT.FINISH, resetContext);
4✔
373

374
      if (axesEvent) {
4✔
375
        axesEvent.setTo({ [AXES.POSITION_KEY]: position }, duration);
2✔
376
      } else {
377
        axes.setTo({ [AXES.POSITION_KEY]: position }, duration);
2✔
378
      }
379
    };
380

381
    return new Promise((resolve, reject) => {
4✔
382
      const animationFinishHandler = () => {
4✔
383
        axes.off(AXES.EVENT.HOLD, interruptionHandler);
2✔
384
        resolve();
2✔
385
      };
386

387
      const interruptionHandler = () => {
4✔
388
        axes.off(AXES.EVENT.FINISH, animationFinishHandler);
389
        reject(new FlickingError(ERROR.MESSAGE.ANIMATION_INTERRUPTED, ERROR.CODE.ANIMATION_INTERRUPTED));
390
      };
391

1✔
392
      axes.once(AXES.EVENT.FINISH, animationFinishHandler);
4✔
393
      axes.once(AXES.EVENT.HOLD, interruptionHandler);
4✔
394

395
      animate();
4✔
396
    });
×
397
  }
398

399
  public updateDirection() {
5✔
UNCOV
400
    const flicking = getFlickingAttached(this._flicking);
×
UNCOV
401
    const axes = this._axes!;
×
UNCOV
402
    const panInput = this._panInput!;
×
403

×
UNCOV
404
    axes.disconnect(panInput);
×
405
    axes.connect(flicking.horizontal ? [AXES.POSITION_KEY, ""] : ["", AXES.POSITION_KEY], panInput);
×
406

407
    panInput.options.scale = flicking.horizontal ? [flicking.camera.panelOrder === ORDER.RTL ? 1 : -1, 0] : [0, -1];
×
408
  }
409

410
  private _resetInternalValues() {
5✔
411
    this._flicking = null;
14✔
412
    this._axes = null;
14✔
413
    this._panInput = null;
14✔
414
    this._animatingContext = { start: 0, end: 0, offset: 0 };
14✔
415
    this._dragged = false;
14✔
416
  }
417

418
  private _onAxesHold = () => {
14✔
419
    this._dragged = false;
3!
420
  };
421

422
  private _onAxesChange = () => {
14✔
423
    this._dragged = !!this._panInput?.isEnabled();
319!
424
  };
425

426
  private _preventClickWhenDragged = (e: MouseEvent) => {
14✔
UNCOV
427
    if (this._dragged) {
×
UNCOV
428
      e.preventDefault();
×
UNCOV
429
      e.stopPropagation();
×
430
    }
431

UNCOV
432
    this._dragged = false;
×
433
  };
434
}
5✔
435

436
export default AxesController;
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc