Skip to content

Commit 1d9bed8

Browse files
authored
feat: add animationThreshold, useCSSOrder option (#938)
* fix: cfc test dependencies * fix: fix bounce 0 for strict control * feat: add animationThreshold option * test: test strict control for bounce 0 * test: test animationThreshold * feat: add useCSSOrder option * fix: add resize scheduling comment * fix: fix resize comment * fix: 주석 보충 설명
1 parent e4fcd5c commit 1d9bed8

File tree

24 files changed

+11895
-23
lines changed

24 files changed

+11895
-23
lines changed

packages/ngx-flicking/projects/ngx-flicking/src/lib/NgxRenderer.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,16 @@ class NgxRenderer extends ExternalRenderer {
5656
const cameraEl = flicking.camera.element;
5757

5858
// We're using reversed panels here as last panel should be the last element of camera element
59-
const reversedElements = this._strategy
60-
.getRenderingElementsByOrder(flicking)
61-
.reverse();
59+
let reversedElements: HTMLElement[] = [];
60+
61+
if (flicking.useCSSOrder) {
62+
// useCSSOrder를 사용하는 경우 DOM은 변화가 없지만 대신 css `order`값을 주입
63+
reversedElements = this.getRenderedPanels().map(panel => panel.element).reverse();
64+
} else {
65+
reversedElements = this._strategy
66+
.getRenderingElementsByOrder(flicking)
67+
.reverse();
68+
}
6269

6370
reversedElements.forEach((el, idx) => {
6471
const nextEl = reversedElements[idx - 1] ? reversedElements[idx - 1] : null;

src/Flicking.ts

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ export interface FlickingOptions {
118118
| MoveTypeOptions<ValueOf<typeof MOVE_TYPE>>;
119119
threshold: number;
120120
dragThreshold: number;
121+
animationThreshold: number;
122+
useCSSOrder: boolean;
121123
interruptable: boolean;
122124
bounce: number | string | [number | string, number | string];
123125
iOSEdgeSwipeThreshold: number;
@@ -200,6 +202,9 @@ class Flicking extends Component<FlickingEvents> {
200202
private _moveType: FlickingOptions["moveType"];
201203
private _threshold: FlickingOptions["threshold"];
202204
private _dragThreshold: FlickingOptions["dragThreshold"];
205+
private _animationThreshold: FlickingOptions["animationThreshold"];
206+
private _useCSSOrder: FlickingOptions["useCSSOrder"];
207+
203208
private _interruptable: FlickingOptions["interruptable"];
204209
private _bounce: FlickingOptions["bounce"];
205210
private _iOSEdgeSwipeThreshold: FlickingOptions["iOSEdgeSwipeThreshold"];
@@ -225,6 +230,7 @@ class Flicking extends Component<FlickingEvents> {
225230
private _initialized: boolean;
226231
private _plugins: Plugin[];
227232
private _isResizing: boolean;
233+
private _scheduleResize = false;
228234

229235
// Components
230236
/**
@@ -703,6 +709,30 @@ class Flicking extends Component<FlickingEvents> {
703709
public get dragThreshold() {
704710
return this._dragThreshold;
705711
}
712+
/**
713+
* The minimum distance for animation to proceed. If the distance to be moved is less than `animationThreshold`, the movement proceeds immediately without animation (duration: 0).
714+
* @ko animation이 진행되기 위한 최소한의 거리. 이동하려는 거리가 `animationThreshold`보다 적으면 애니메이션 없이(duration: 0) 즉시 이동한다.
715+
* @type {number}
716+
* @default 0.5
717+
* @see {@link https://naver.github.io/egjs-flicking/Options#animationThreshold animationThreshold ( Options )}
718+
*/
719+
public get animationThreshold() {
720+
return this._animationThreshold;
721+
}
722+
/**
723+
* Using `useCSSOrder` does not change the DOM order, but the `order` CSS property changes the order on the screen. (When `circular` is used, the DOM order changes depending on the position.)
724+
* When using `iframe`, you can prevent reloading when the DOM order changes.
725+
* In svelte, CSS order is always used.
726+
* @ko `useCSSOrder`를 사용하면 DOM의 순서는 변경되지 않지만 `order` css가 설정되면서 화면상 순서가 바뀐다. (`circular`를 사용한 경우 위치에 따라 DOM의 순서가 변경된다.)
727+
* `iframe`을 사용하는 경우 DOM의 순서가 변경되면서 reload가 되는 것을 막을 수 있다.
728+
* svelte에서는 css order를 무조건 사용한다.
729+
* @type {boolean}
730+
* @default false
731+
* @see {@link https://naver.github.io/egjs-flicking/Options#useCSSOrder useCSSOrder ( Options )}
732+
*/
733+
public get useCSSOrder() {
734+
return this._useCSSOrder;
735+
}
706736

707737
/**
708738
* Set animation to be interruptable by click/touch.
@@ -1117,6 +1147,12 @@ class Flicking extends Component<FlickingEvents> {
11171147
panInput.options.threshold = val;
11181148
}
11191149
}
1150+
public set animationThreshold(val: FlickingOptions["animationThreshold"]) {
1151+
this._animationThreshold = val;
1152+
}
1153+
public set useCSSOrder(val: FlickingOptions["useCSSOrder"]) {
1154+
this._useCSSOrder = val;
1155+
}
11201156

11211157
public set interruptable(val: FlickingOptions["interruptable"]) {
11221158
this._interruptable = val;
@@ -1291,7 +1327,9 @@ class Flicking extends Component<FlickingEvents> {
12911327
useFractionalSize = false,
12921328
externalRenderer = null,
12931329
renderExternal = null,
1294-
optimizeSizeUpdate = false
1330+
optimizeSizeUpdate = false,
1331+
animationThreshold = 0.5,
1332+
useCSSOrder = false,
12951333
}: Partial<FlickingOptions> = {}) {
12961334
super();
12971335

@@ -1340,6 +1378,8 @@ class Flicking extends Component<FlickingEvents> {
13401378
this._externalRenderer = externalRenderer;
13411379
this._renderExternal = renderExternal;
13421380
this._optimizeSizeUpdate = optimizeSizeUpdate;
1381+
this._animationThreshold = animationThreshold;
1382+
this._useCSSOrder = useCSSOrder;
13431383

13441384
// Create core components
13451385
this._viewport = new Viewport(this, getElement(root));
@@ -1423,7 +1463,9 @@ class Flicking extends Component<FlickingEvents> {
14231463

14241464
this._plugins.forEach((plugin) => plugin.destroy());
14251465

1466+
this._scheduleResize = false;
14261467
this._initialized = false;
1468+
this._isResizing = false;
14271469
}
14281470

14291471
/**
@@ -1821,11 +1863,20 @@ class Flicking extends Component<FlickingEvents> {
18211863
* @method
18221864
* @fires Flicking#beforeResize
18231865
* @fires Flicking#afterResize
1824-
* @return {this}
1866+
* @return {boolean}
18251867
*/
18261868
public async resize(): Promise<void> {
1827-
if (this._isResizing) return;
1869+
if (!this._initialized) {
1870+
return;
1871+
}
1872+
if (this._isResizing) {
1873+
// resize를 연속으로 발생하면 무시하기에 마지막 viewport를 사이즈를 알 수 없음.
1874+
// resize를 1번 더 실행할 수 잇는 스케줄링 등록
1875+
this._scheduleResize = true;
1876+
return;
1877+
}
18281878

1879+
this._scheduleResize = false;
18291880
this._isResizing = true;
18301881

18311882
const viewport = this._viewport;
@@ -1872,6 +1923,7 @@ class Flicking extends Component<FlickingEvents> {
18721923
camera.updatePanelOrder();
18731924
camera.updateOffset();
18741925
await renderer.render();
1926+
18751927
if (!this._initialized) {
18761928
return;
18771929
}
@@ -1901,6 +1953,12 @@ class Flicking extends Component<FlickingEvents> {
19011953
);
19021954

19031955
this._isResizing = false;
1956+
1957+
// 연속으로 resize를 호출하는 경우를 대비하기 위해서 스케줄링 반영
1958+
if (this._scheduleResize) {
1959+
this.resize();
1960+
}
1961+
return;
19041962
}
19051963

19061964
/**
@@ -2098,8 +2156,8 @@ class Flicking extends Component<FlickingEvents> {
20982156
const nearestAnchor = camera.findNearestAnchor(defaultPanel.position);
20992157
const initialPanel =
21002158
nearestAnchor &&
2101-
defaultPanel.position !== nearestAnchor.panel.position &&
2102-
defaultPanel.index !== nearestAnchor.panel.index
2159+
defaultPanel.position !== nearestAnchor.panel.position &&
2160+
defaultPanel.index !== nearestAnchor.panel.index
21032161
? nearestAnchor.panel
21042162
: defaultPanel;
21052163
control.setActive(initialPanel, null, false);

src/cfc/getRenderingPanels.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@ export default <T>(flicking: Flicking, diffResult: DiffResult<T>) => {
1212
map[prev] = current;
1313
return map;
1414
}, {});
15+
const renderingPanels = flicking.panels
16+
.filter(panel => !removedPanels[panel.index]);
17+
18+
19+
if (!flicking.useCSSOrder) {
20+
// useCSSOrder를 사용하게 되는 경우 sort를 하지 않는다.
21+
renderingPanels.sort((panel1, panel2) => (panel1.position + panel1.offset) - (panel2.position + panel2.offset));
22+
}
1523

1624
return [
17-
...flicking.panels
18-
.filter(panel => !removedPanels[panel.index])
19-
// Sort panels by position
20-
.sort((panel1, panel2) => (panel1.position + panel1.offset) - (panel2.position + panel2.offset))
21-
.map(panel => diffResult.list[maintainedMap[panel.index]]),
25+
...renderingPanels.map(panel => diffResult.list[maintainedMap[panel.index]]),
2226
...diffResult.added.map(idx => diffResult.list[idx])
2327
];
2428
};
25-

src/control/AxesController.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ class AxesController {
345345
return Promise.reject(new FlickingError(ERROR.MESSAGE.NOT_ATTACHED_TO_FLICKING, ERROR.CODE.NOT_ATTACHED_TO_FLICKING));
346346
}
347347

348-
const startPos = axes.get([AXES.POSITION_KEY])[AXES.POSITION_KEY];
348+
const startPos = this.getCurrentPosition();
349349

350350
if (startPos === position) {
351351
const flicking = getFlickingAttached(this._flicking);
@@ -396,6 +396,14 @@ class AxesController {
396396
});
397397
}
398398

399+
/**
400+
* Returns the current axes position
401+
* @ko 현재 axes의 position을 반환합니다.
402+
*/
403+
public getCurrentPosition() {
404+
return this._axes?.get([AXES.POSITION_KEY])[AXES.POSITION_KEY] ?? 0;
405+
}
406+
399407
public updateDirection() {
400408
const flicking = getFlickingAttached(this._flicking);
401409
const axes = this._axes!;

src/control/Control.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,12 +380,19 @@ abstract class Control {
380380
axesEvent?: OnRelease;
381381
}) {
382382
const flicking = getFlickingAttached(this._flicking);
383-
const animate = () => this._controller.animateTo(position, duration, axesEvent);
383+
384+
// 거리(1px 미만)가 매우 짧은 경우 duration이 늘어지는걸 방지하기 위해 0으로 바꿔 즉시 변경
385+
let nextDuration = duration;
386+
387+
if (Math.abs(nextDuration - position) < flicking.animationThreshold) {
388+
nextDuration = 0;
389+
}
390+
const animate = () => this._controller.animateTo(position, nextDuration, axesEvent);
384391
const state = this._controller.state;
385392

386393
state.targetPanel = newActivePanel;
387394

388-
if (duration <= 0) {
395+
if (nextDuration <= 0) {
389396
return animate();
390397
} else {
391398
return animate().then(async () => {

src/control/StrictControl.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,9 @@ class StrictControl extends Control {
220220
const firstAnchor = anchors[0];
221221
const lastAnchor = anchors[anchors.length - 1];
222222

223-
const shouldBounceToFirst = position <= cameraRange.min && isBetween(firstAnchor.panel.index, indexRange.min, indexRange.max);
224-
const shouldBounceToLast = position >= cameraRange.max && isBetween(lastAnchor.panel.index, indexRange.min, indexRange.max);
223+
// position이 bounce으로 인하여 범위를 넘어가야 동작하도록 변경
224+
const shouldBounceToFirst = position < cameraRange.min && isBetween(firstAnchor.panel.index, indexRange.min, indexRange.max);
225+
const shouldBounceToLast = position > cameraRange.max && isBetween(lastAnchor.panel.index, indexRange.min, indexRange.max);
225226

226227
const isAdjacent = adjacentAnchor && (indexRange.min <= indexRange.max
227228
? isBetween(adjacentAnchor.index, indexRange.min, indexRange.max)

src/renderer/Renderer.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,17 @@ abstract class Renderer {
152152
return Promise.resolve();
153153
}
154154

155+
/**
156+
* Return Rendered Panels
157+
* @ko 렌더링이 된 패널을 반환합니다.
158+
* @return {Panel[]}
159+
*/
160+
public getRenderedPanels() {
161+
const flicking = getFlickingAttached(this._flicking);
162+
163+
return flicking.renderer.panels.filter(panel => panel.rendered);
164+
}
165+
155166
/**
156167
* Update all panel sizes
157168
* @ko 모든 패널의 크기를 업데이트합니다
@@ -554,6 +565,15 @@ abstract class Renderer {
554565
const flicking = getFlickingAttached(this._flicking);
555566

556567
flicking.camera.applyTransform();
568+
569+
if (flicking.useCSSOrder) {
570+
// useCSSOrder를 사용하는 경우 DOM은 변화가 없지만 대신 css `order`값을 주입
571+
const panels = flicking.panels;
572+
573+
this._strategy.getRenderingIndexesByOrder(flicking).forEach((domIndex, index) => {
574+
panels[domIndex].element.style.order = `${index}`;
575+
});
576+
}
557577
}
558578
}
559579

src/renderer/VanillaRenderer.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,17 @@ class VanillaRenderer extends Renderer {
4040
const cameraEl = flicking.camera.element;
4141

4242
// We're using reversed panels here as last panel should be the last element of camera element
43-
const reversedElements = this._strategy
44-
.getRenderingElementsByOrder(flicking)
45-
.reverse();
43+
44+
let reversedElements: HTMLElement[] = [];
45+
46+
if (flicking.useCSSOrder) {
47+
// useCSSOrder를 사용하는 경우 DOM은 변화가 없지만 대신 css `order`값을 주입
48+
reversedElements = this.getRenderedPanels().map(panel => panel.element).reverse();
49+
} else {
50+
reversedElements = this._strategy
51+
.getRenderingElementsByOrder(flicking)
52+
.reverse();
53+
}
4654

4755
reversedElements.forEach((el, idx) => {
4856
const nextEl = reversedElements[idx - 1] ? reversedElements[idx - 1] : null;

test/cfc/framework/react/package-lock.json

Lines changed: 52 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/cfc/framework/react/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"test": "jest --config=\"./jest.config.js\""
66
},
77
"devDependencies": {
8+
"@cfcs/react": "^0.1.0",
89
"@testing-library/react": "^12.1.2"
910
}
1011
}

0 commit comments

Comments
 (0)