Transition: Fixes for controlled swipe
This commit is contained in:
parent
e77506cc54
commit
939fff460e
@ -97,11 +97,12 @@ function Transition({
|
|||||||
const rendersRef = useRef<Record<number, React.ReactNode | ChildrenFn>>({});
|
const rendersRef = useRef<Record<number, React.ReactNode | ChildrenFn>>({});
|
||||||
const prevActiveKey = usePrevious<any>(activeKey);
|
const prevActiveKey = usePrevious<any>(activeKey);
|
||||||
const forceUpdate = useForceUpdate();
|
const forceUpdate = useForceUpdate();
|
||||||
|
const isAnimatingRef = useRef(false);
|
||||||
const isSwipeJustCancelledRef = useRef(false);
|
const isSwipeJustCancelledRef = useRef(false);
|
||||||
|
|
||||||
const activeKeyChanged = prevActiveKey !== undefined && activeKey !== prevActiveKey;
|
const hasActiveKeyChanged = prevActiveKey !== undefined && activeKey !== prevActiveKey;
|
||||||
|
|
||||||
if (!renderCount && activeKeyChanged) {
|
if (!renderCount && hasActiveKeyChanged) {
|
||||||
rendersRef.current = { [prevActiveKey]: rendersRef.current[prevActiveKey] };
|
rendersRef.current = { [prevActiveKey]: rendersRef.current[prevActiveKey] };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,25 +150,26 @@ function Transition({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!activeKeyChanged) {
|
if (!hasActiveKeyChanged) {
|
||||||
if (childElements.length === 1 || (nextKey !== undefined && childElements.length === 2)) {
|
if (isAnimatingRef.current) {
|
||||||
const firstChild = childNodes[activeIndex] as HTMLElement;
|
return;
|
||||||
|
|
||||||
addExtraClass(firstChild, CLASSES.active);
|
|
||||||
|
|
||||||
if (isSlideOptimized) {
|
|
||||||
setExtraStyles(firstChild, {
|
|
||||||
transition: 'none',
|
|
||||||
transform: 'translate3d(0, 0, 0)',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (childElements.length === 2) {
|
|
||||||
const nextChild = childElements[0] === firstChild ? childElements[1] : childElements[0];
|
|
||||||
addExtraClass(nextChild, CLASSES.inactive);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
childElements.forEach((childElement) => {
|
||||||
|
if (childElement === childNodes[activeIndex]) {
|
||||||
|
addExtraClass(childElement, CLASSES.active);
|
||||||
|
|
||||||
|
if (isSlideOptimized) {
|
||||||
|
setExtraStyles(childElement, {
|
||||||
|
transition: 'none',
|
||||||
|
transform: 'translate3d(0, 0, 0)',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addExtraClass(childElement, CLASSES.inactive);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,6 +187,7 @@ function Transition({
|
|||||||
cleanup,
|
cleanup,
|
||||||
activeKey,
|
activeKey,
|
||||||
currentKeyRef,
|
currentKeyRef,
|
||||||
|
isAnimatingRef,
|
||||||
container,
|
container,
|
||||||
childNodes[activeIndex],
|
childNodes[activeIndex],
|
||||||
childNodes[prevActiveIndex],
|
childNodes[prevActiveIndex],
|
||||||
@ -224,6 +227,7 @@ function Transition({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
isAnimatingRef.current = true;
|
||||||
const dispatchHeavyAnimationStop = dispatchHeavyAnimationEvent();
|
const dispatchHeavyAnimationStop = dispatchHeavyAnimationEvent();
|
||||||
onStart?.();
|
onStart?.();
|
||||||
|
|
||||||
@ -260,6 +264,7 @@ function Transition({
|
|||||||
|
|
||||||
onStop?.();
|
onStop?.();
|
||||||
dispatchHeavyAnimationStop();
|
dispatchHeavyAnimationStop();
|
||||||
|
isAnimatingRef.current = false;
|
||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
});
|
});
|
||||||
@ -281,6 +286,7 @@ function Transition({
|
|||||||
isSwipeJustCancelledRef.current = true;
|
isSwipeJustCancelledRef.current = true;
|
||||||
onStop?.();
|
onStop?.();
|
||||||
dispatchHeavyAnimationStop();
|
dispatchHeavyAnimationStop();
|
||||||
|
isAnimatingRef.current = false;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -293,7 +299,7 @@ function Transition({
|
|||||||
activeKey,
|
activeKey,
|
||||||
nextKey,
|
nextKey,
|
||||||
prevActiveKey,
|
prevActiveKey,
|
||||||
activeKeyChanged,
|
hasActiveKeyChanged,
|
||||||
isBackwards,
|
isBackwards,
|
||||||
name,
|
name,
|
||||||
onStart,
|
onStart,
|
||||||
@ -373,6 +379,7 @@ function performSlideOptimized(
|
|||||||
cleanup: NoneToVoidFunction,
|
cleanup: NoneToVoidFunction,
|
||||||
activeKey: number,
|
activeKey: number,
|
||||||
currentKeyRef: { current: number | undefined },
|
currentKeyRef: { current: number | undefined },
|
||||||
|
isAnimatingRef: { current: boolean | undefined },
|
||||||
container: HTMLElement,
|
container: HTMLElement,
|
||||||
toSlide: ChildNode,
|
toSlide: ChildNode,
|
||||||
fromSlide?: ChildNode,
|
fromSlide?: ChildNode,
|
||||||
@ -409,8 +416,8 @@ function performSlideOptimized(
|
|||||||
isBackwards = !isBackwards;
|
isBackwards = !isBackwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isAnimatingRef.current = true;
|
||||||
const dispatchHeavyAnimationStop = dispatchHeavyAnimationEvent();
|
const dispatchHeavyAnimationStop = dispatchHeavyAnimationEvent();
|
||||||
|
|
||||||
onStart?.();
|
onStart?.();
|
||||||
|
|
||||||
toggleExtraClass(container, `Transition-${name}`, !isBackwards);
|
toggleExtraClass(container, `Transition-${name}`, !isBackwards);
|
||||||
@ -476,6 +483,8 @@ function performSlideOptimized(
|
|||||||
|
|
||||||
onStop?.();
|
onStop?.();
|
||||||
dispatchHeavyAnimationStop();
|
dispatchHeavyAnimationStop();
|
||||||
|
isAnimatingRef.current = false;
|
||||||
|
|
||||||
cleanup();
|
cleanup();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -56,7 +56,7 @@ type AnimateNumberProps<T extends number | number[]> = {
|
|||||||
duration: number;
|
duration: number;
|
||||||
onUpdate: (value: T) => void;
|
onUpdate: (value: T) => void;
|
||||||
timing?: TimingFn;
|
timing?: TimingFn;
|
||||||
onEnd?: () => void;
|
onEnd?: (isCanceled?: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const timingFunctions = {
|
export const timingFunctions = {
|
||||||
@ -87,13 +87,15 @@ export function animateNumber<T extends number | number[]>({
|
|||||||
to,
|
to,
|
||||||
}: AnimateNumberProps<T>) {
|
}: AnimateNumberProps<T>) {
|
||||||
const t0 = Date.now();
|
const t0 = Date.now();
|
||||||
let canceled = false;
|
|
||||||
|
let isCanceled = false;
|
||||||
|
|
||||||
animateInstantly(() => {
|
animateInstantly(() => {
|
||||||
if (canceled) return false;
|
if (isCanceled) return false;
|
||||||
|
|
||||||
const t1 = Date.now();
|
const t1 = Date.now();
|
||||||
let t = (t1 - t0) / duration;
|
const t = Math.min((t1 - t0) / duration, 1);
|
||||||
if (t > 1) t = 1;
|
|
||||||
const progress = timing(t);
|
const progress = timing(t);
|
||||||
if (typeof from === 'number' && typeof to === 'number') {
|
if (typeof from === 'number' && typeof to === 'number') {
|
||||||
onUpdate((from + ((to - from) * progress)) as T);
|
onUpdate((from + ((to - from) * progress)) as T);
|
||||||
@ -101,13 +103,17 @@ export function animateNumber<T extends number | number[]>({
|
|||||||
const result = from.map((f, i) => f + ((to[i] - f) * progress));
|
const result = from.map((f, i) => f + ((to[i] - f) * progress));
|
||||||
onUpdate(result as T);
|
onUpdate(result as T);
|
||||||
}
|
}
|
||||||
if (t === 1 && onEnd) onEnd();
|
|
||||||
|
if (t === 1) {
|
||||||
|
onEnd?.();
|
||||||
|
}
|
||||||
|
|
||||||
return t < 1;
|
return t < 1;
|
||||||
}, requestMeasure);
|
}, requestMeasure);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
canceled = true;
|
isCanceled = true;
|
||||||
if (onEnd) onEnd();
|
onEnd?.(true);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,11 +38,10 @@ export function logUnequalProps(currentProps: AnyLiteral, newProps: AnyLiteral,
|
|||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(msg);
|
console.log(msg);
|
||||||
|
currentKeys.forEach((prop) => {
|
||||||
for (const prop of currentKeys) {
|
|
||||||
if (currentProps[prop] !== newProps[prop]) {
|
if (currentProps[prop] !== newProps[prop]) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(debugKey, prop, ':', currentProps[prop], '=>', newProps[prop]);
|
console.log(debugKey, prop, ':', currentProps[prop], '=>', newProps[prop]);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ let isSwipeActive = false;
|
|||||||
let swipeOffsets: MoveOffsets | undefined;
|
let swipeOffsets: MoveOffsets | undefined;
|
||||||
let onDrag: ((offsets: MoveOffsets) => void) | undefined;
|
let onDrag: ((offsets: MoveOffsets) => void) | undefined;
|
||||||
let onRelease: ((onCancel: NoneToVoidFunction) => void) | undefined;
|
let onRelease: ((onCancel: NoneToVoidFunction) => void) | undefined;
|
||||||
|
let cancelCurrentReleaseAnimation: NoneToVoidFunction | undefined;
|
||||||
|
|
||||||
export function captureControlledSwipe(
|
export function captureControlledSwipe(
|
||||||
element: HTMLElement, options: {
|
element: HTMLElement, options: {
|
||||||
@ -70,6 +71,8 @@ export function allowSwipeControlForTransition(
|
|||||||
nextSlide: HTMLElement,
|
nextSlide: HTMLElement,
|
||||||
onCancelForTransition: NoneToVoidFunction,
|
onCancelForTransition: NoneToVoidFunction,
|
||||||
) {
|
) {
|
||||||
|
cancelCurrentReleaseAnimation?.();
|
||||||
|
|
||||||
if (!isSwipeActive) return;
|
if (!isSwipeActive) return;
|
||||||
|
|
||||||
const targetPosition = extractAnimationEndPosition(currentSlide);
|
const targetPosition = extractAnimationEndPosition(currentSlide);
|
||||||
@ -108,7 +111,7 @@ export function allowSwipeControlForTransition(
|
|||||||
};
|
};
|
||||||
|
|
||||||
onRelease = (onCancelForClient: NoneToVoidFunction) => {
|
onRelease = (onCancelForClient: NoneToVoidFunction) => {
|
||||||
const isCanceled = currentDirection === -1;
|
const isRevertSwipe = currentDirection === -1;
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
currentSlide.getAnimations().forEach((a) => a.cancel());
|
currentSlide.getAnimations().forEach((a) => a.cancel());
|
||||||
@ -120,21 +123,23 @@ export function allowSwipeControlForTransition(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isCanceled) {
|
if (!isRevertSwipe) {
|
||||||
// For some reason animations are not cleared when CSS class is removed
|
// For some reason animations are not cleared when CSS class is removed
|
||||||
waitForAnimationEnd(currentSlide, cleanup);
|
waitForAnimationEnd(currentSlide, cleanup);
|
||||||
}
|
}
|
||||||
|
|
||||||
animateNumber({
|
cancelCurrentReleaseAnimation = animateNumber({
|
||||||
from: progress,
|
from: progress,
|
||||||
to: isCanceled ? 0 : 1,
|
to: isRevertSwipe ? 0 : 1,
|
||||||
duration: INERTIA_DURATION,
|
duration: INERTIA_DURATION,
|
||||||
timing: INERTIA_EASING,
|
timing: INERTIA_EASING,
|
||||||
onUpdate(releaseProgress) {
|
onUpdate(releaseProgress) {
|
||||||
updateAnimationProgress([currentSlide, nextSlide], releaseProgress);
|
updateAnimationProgress([currentSlide, nextSlide], releaseProgress);
|
||||||
},
|
},
|
||||||
onEnd() {
|
onEnd(isCanceled = false) {
|
||||||
if (isCanceled) {
|
cancelCurrentReleaseAnimation = undefined;
|
||||||
|
|
||||||
|
if (isCanceled || isRevertSwipe) {
|
||||||
cleanup();
|
cleanup();
|
||||||
onCancelForTransition();
|
onCancelForTransition();
|
||||||
onCancelForClient();
|
onCancelForClient();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user