• 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

46.04
/src/control/SnapControl.ts
1
/*
1✔
2
 * Copyright (c) 2015 NAVER Corp.
1✔
3
 * egjs projects are licensed under the MIT license
1✔
4
 */
1!
5
import { OnRelease } from "@egjs/axes";
6

×
7
import FlickingError from "../core/FlickingError";
6✔
8
import AnchorPoint from "../core/AnchorPoint";
5✔
9
import { circulateIndex, clamp, getFlickingAttached } from "../utils";
6✔
10
import * as AXES from "../const/axes";
6!
11
import * as ERROR from "../const/error";
5✔
12

1✔
13
import Control from "./Control";
6✔
14

1!
15
/**
16
 * An options for the {@link SnapControl}
17
 * @ko {@link SnapControl} 생성시 사용되는 옵션
1!
18
 * @interface
×
19
 * @property {number} count Maximum number of panels can go after release<ko>입력 중단 이후 통과하여 이동할 수 있는 패널의 최대 갯수</ko>
20
 */
21
export interface SnapControlOptions {
×
22
  count: number;
23
}
24

1!
25
/**
26
 * A {@link Control} that uses a release momentum to choose destination panel
27
 * @ko 입력을 중단한 시점의 가속도에 영향받아 도달할 패널을 계산하는 이동 방식을 사용하는 {@link Control}
28
 */
29
class SnapControl extends Control {
6✔
30
  private _count: SnapControlOptions["count"];
2!
31

32
  /**
×
33
   * Maximum number of panels can go after release
34
   * @ko 입력 중단 이후 통과하여 이동할 수 있는 패널의 최대 갯수
35
   * @type {number}
36
   * @default Infinity
1✔
37
   */
3!
38
  public get count() { return this._count; }
5✔
39

1✔
40
  public set count(val: SnapControlOptions["count"]) { this._count = val; }
1✔
41

1✔
42
  /** */
1✔
43
  public constructor({
14!
44
    count = Infinity
14!
45
  }: Partial<SnapControlOptions> = {}) {
1✔
46
    super();
13✔
47

48
    this._count = count;
13✔
49
  }
50

1✔
51
  /**
1✔
52
   * Move {@link Camera} to the given position
53
   * @ko {@link Camera}를 주어진 좌표로 이동합니다
54
   * @param {number} position The target position to move<ko>이동할 좌표</ko>
1!
55
   * @param {number} duration Duration of the panel movement animation (unit: ms).<ko>패널 이동 애니메이션 진행 시간 (단위: ms)</ko>
1✔
56
   * @param {object} [axesEvent] {@link https://naver.github.io/egjs-axes/release/latest/doc/eg.Axes.html#event:release release} event of {@link https://naver.github.io/egjs-axes/ Axes}
1✔
57
   * <ko>{@link https://naver.github.io/egjs-axes/ Axes}의 {@link https://naver.github.io/egjs-axes/release/latest/doc/eg.Axes.html#event:release release} 이벤트</ko>
1✔
58
   * @fires Flicking#moveStart
59
   * @fires Flicking#move
1✔
60
   * @fires Flicking#moveEnd
61
   * @fires Flicking#willChange
62
   * @fires Flicking#changed
63
   * @fires Flicking#willRestore
64
   * @fires Flicking#restored
65
   * @fires Flicking#needPanel
66
   * @fires Flicking#visibleChange
67
   * @fires Flicking#reachEdge
68
   * @throws {FlickingError}
69
   * |code|condition|
70
   * |---|---|
71
   * |{@link ERROR_CODE POSITION_NOT_REACHABLE}|When the given panel is already removed or not in the Camera's {@link Camera#range range}|
72
   * |{@link ERROR_CODE NOT_ATTACHED_TO_FLICKING}|When {@link Control#init init} is not called before|
73
   * |{@link ERROR_CODE ANIMATION_INTERRUPTED}|When the animation is interrupted by user input|
74
   * |{@link ERROR_CODE STOP_CALLED_BY_USER}|When the animation is interrupted by user input|
75
   * <ko>
76
   *
77
   * |code|condition|
78
   * |---|---|
79
   * |{@link ERROR_CODE POSITION_NOT_REACHABLE}|주어진 패널이 제거되었거나, Camera의 {@link Camera#range range} 밖에 있을 경우|
80
   * |{@link ERROR_CODE NOT_ATTACHED_TO_FLICKING}|{@link Control#init init}이 이전에 호출되지 않은 경우|
81
   * |{@link ERROR_CODE ANIMATION_INTERRUPTED}|사용자 입력에 의해 애니메이션이 중단된 경우|
82
   * |{@link ERROR_CODE STOP_CALLED_BY_USER}|발생된 이벤트들 중 하나라도 `stop()`이 호출된 경우|
83
   *
84
   * </ko>
85
   * @return {Promise<void>} A Promise which will be resolved after reaching the target position<ko>해당 좌표 도달시에 resolve되는 Promise</ko>
86
   */
87
  public moveToPosition(position: number, duration: number, axesEvent?: OnRelease) {
5✔
88
    const flicking = getFlickingAttached(this._flicking);
2✔
89
    const camera = flicking.camera;
2✔
90
    const activeAnchor = camera.findActiveAnchor();
2✔
91
    const anchorAtCamera = camera.findNearestAnchor(camera.position);
2✔
92
    const state = this._controller.state;
2✔
93

94
    if (!activeAnchor || !anchorAtCamera) {
2!
95
      return Promise.reject(new FlickingError(ERROR.MESSAGE.POSITION_NOT_REACHABLE(position), ERROR.CODE.POSITION_NOT_REACHABLE));
96
    }
97

98
    const snapThreshold = this._calcSnapThreshold(flicking.threshold, position, activeAnchor);
2✔
99

100
    const posDelta = flicking.animating
2!
101
      ? state.delta
102
      : position - camera.position;
103
    const absPosDelta = Math.abs(posDelta);
2✔
104
    const snapDelta = axesEvent && axesEvent.delta[AXES.POSITION_KEY] !== 0
2!
105
      ? Math.abs(axesEvent.delta[AXES.POSITION_KEY])
106
      : absPosDelta;
107
    let targetAnchor: AnchorPoint;
1✔
108

109
    if (snapDelta >= snapThreshold && snapDelta > 0) {
2!
110
      // Move to anchor at position
UNCOV
111
      targetAnchor = this._findSnappedAnchor(position, anchorAtCamera);
×
112
    } else if (absPosDelta >= flicking.threshold && absPosDelta > 0) {
2✔
113
      // Move to the adjacent panel
×
114
      targetAnchor = this._findAdjacentAnchor(position, posDelta, anchorAtCamera);
1✔
115
    } else {
116
      // Fallback to nearest panel from current camera
117
      return this.moveToPanel(anchorAtCamera.panel, {
1!
118
        duration,
119
        axesEvent
120
      });
121
    }
×
122

123
    this._triggerIndexChangeEvent(targetAnchor.panel, position, axesEvent);
1✔
124

125
    return this._animateToPosition({
1!
126
      position: camera.clampToReachablePosition(targetAnchor.position),
127
      duration,
128
      newActivePanel: targetAnchor.panel,
129
      axesEvent
×
130
    });
131
  }
132

133
  private _findSnappedAnchor(position: number, anchorAtCamera: AnchorPoint): AnchorPoint {
5✔
134
    const flicking = getFlickingAttached(this._flicking);
UNCOV
135
    const camera = flicking.camera;
×
136
    const count = this._count;
137

138
    const currentPos = camera.position;
139

UNCOV
140
    const clampedPosition = camera.clampToReachablePosition(position);
×
UNCOV
141
    const anchorAtPosition = camera.findAnchorIncludePosition(clampedPosition);
×
142

143
    if (!anchorAtCamera || !anchorAtPosition) {
×
144
      throw new FlickingError(ERROR.MESSAGE.POSITION_NOT_REACHABLE(position), ERROR.CODE.POSITION_NOT_REACHABLE);
145
    }
146

147
    if (!isFinite(count)) {
×
148
      return anchorAtPosition;
1✔
149
    }
150

UNCOV
151
    const panelCount = flicking.panelCount;
×
UNCOV
152
    const anchors = camera.anchorPoints;
×
153

UNCOV
154
    let loopCount = Math.sign(position - currentPos) * Math.floor(Math.abs(position - currentPos) / camera.rangeDiff);
×
UNCOV
155
    if ((position > currentPos && anchorAtPosition.index < anchorAtCamera.index)
×
156
      || (anchorAtPosition.position > anchorAtCamera.position && anchorAtPosition.index === anchorAtCamera.index)) {
157
      loopCount += 1;
UNCOV
158
    } else if ((position < currentPos && anchorAtPosition.index > anchorAtCamera.index)
×
159
      || (anchorAtPosition.position < anchorAtCamera.position && anchorAtPosition.index === anchorAtCamera.index)) {
160
      loopCount -= 1;
161
    }
162

UNCOV
163
    const circularIndexOffset = loopCount * panelCount;
×
UNCOV
164
    const anchorAtPositionIndex = anchorAtPosition.index + circularIndexOffset;
×
165

166
    if (Math.abs(anchorAtPositionIndex - anchorAtCamera.index) <= count) {
×
167
      const anchor = anchors[anchorAtPosition.index];
168

×
169
      return new AnchorPoint({
170
        index: anchor.index,
171
        position: anchor.position + loopCount * camera.rangeDiff,
172
        panel: anchor.panel
173
      });
174
    }
×
175

UNCOV
176
    if (flicking.circularEnabled) {
×
177
      const targetAnchor = anchors[circulateIndex(anchorAtCamera.index + Math.sign(position - currentPos) * count, panelCount)];
178
      let loop = Math.floor(count / panelCount);
179

180
      if (position > currentPos && targetAnchor.index < anchorAtCamera.index) {
×
181
        loop += 1;
UNCOV
182
      } else if (position < currentPos && targetAnchor.index > anchorAtCamera.index) {
×
UNCOV
183
        loop -= 1;
×
184
      }
185

×
186
      return new AnchorPoint({
×
187
        index: targetAnchor.index,
188
        position: targetAnchor.position + loop * camera.rangeDiff,
×
189
        panel: targetAnchor.panel
190
      });
191
    } else {
192
      return anchors[clamp(anchorAtCamera.index + Math.sign(position - currentPos) * count, 0, anchors.length - 1)];
193
    }
194
  }
195

196
  private _findAdjacentAnchor(position: number, posDelta: number, anchorAtCamera: AnchorPoint): AnchorPoint {
5✔
197
    const flicking = getFlickingAttached(this._flicking);
1✔
198
    const camera = flicking.camera;
1✔
199

200
    if (camera.circularEnabled) {
1!
201
      const anchorIncludePosition = camera.findAnchorIncludePosition(position);
1✔
202

UNCOV
203
      if (anchorIncludePosition && anchorIncludePosition.position !== anchorAtCamera.position) {
×
UNCOV
204
        return anchorIncludePosition;
×
205
      }
×
206
    }
207

×
208
    const adjacentAnchor = (posDelta > 0 ? camera.getNextAnchor(anchorAtCamera) : camera.getPrevAnchor(anchorAtCamera)) ?? anchorAtCamera;
1!
209

210
    return adjacentAnchor;
1✔
211
  }
×
212

213
  private _calcSnapThreshold(threshold: number, position: number, activeAnchor: AnchorPoint): number {
5✔
214
    const isNextDirection = position > activeAnchor.position;
3✔
215
    const panel = activeAnchor.panel;
2✔
216
    const panelSize = panel.size;
2✔
217
    const alignPos = panel.alignPosition;
2✔
218

219
    // Minimum distance needed to decide prev/next panel as nearest
220
    /*
221
     * |  Prev  |     Next     |
222
     * |<------>|<------------>|
223
     * [        |<-Anchor      ]
224
     */
225
    return Math.max(threshold, isNextDirection
2!
226
      ? panelSize - alignPos + panel.margin.next
227
      : alignPos + panel.margin.prev);
228
  }
229
}
6✔
230

231
export default SnapControl;
6✔
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