Transition: Fixes for controlled swipe

This commit is contained in:
Alexander Zinchuk 2023-12-04 14:38:16 +01:00
parent e77506cc54
commit 939fff460e
4 changed files with 57 additions and 38 deletions

View File

@ -97,11 +97,12 @@ function Transition({
const rendersRef = useRef<Record<number, React.ReactNode | ChildrenFn>>({});
const prevActiveKey = usePrevious<any>(activeKey);
const forceUpdate = useForceUpdate();
const isAnimatingRef = 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] };
}
@ -149,25 +150,26 @@ function Transition({
}
});
if (!activeKeyChanged) {
if (childElements.length === 1 || (nextKey !== undefined && childElements.length === 2)) {
const firstChild = childNodes[activeIndex] as HTMLElement;
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);
}
if (!hasActiveKeyChanged) {
if (isAnimatingRef.current) {
return;
}
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;
}
@ -185,6 +187,7 @@ function Transition({
cleanup,
activeKey,
currentKeyRef,
isAnimatingRef,
container,
childNodes[activeIndex],
childNodes[prevActiveIndex],
@ -224,6 +227,7 @@ function Transition({
}
});
isAnimatingRef.current = true;
const dispatchHeavyAnimationStop = dispatchHeavyAnimationEvent();
onStart?.();
@ -260,6 +264,7 @@ function Transition({
onStop?.();
dispatchHeavyAnimationStop();
isAnimatingRef.current = false;
cleanup();
});
@ -281,6 +286,7 @@ function Transition({
isSwipeJustCancelledRef.current = true;
onStop?.();
dispatchHeavyAnimationStop();
isAnimatingRef.current = false;
},
);
} else {
@ -293,7 +299,7 @@ function Transition({
activeKey,
nextKey,
prevActiveKey,
activeKeyChanged,
hasActiveKeyChanged,
isBackwards,
name,
onStart,
@ -373,6 +379,7 @@ function performSlideOptimized(
cleanup: NoneToVoidFunction,
activeKey: number,
currentKeyRef: { current: number | undefined },
isAnimatingRef: { current: boolean | undefined },
container: HTMLElement,
toSlide: ChildNode,
fromSlide?: ChildNode,
@ -409,8 +416,8 @@ function performSlideOptimized(
isBackwards = !isBackwards;
}
isAnimatingRef.current = true;
const dispatchHeavyAnimationStop = dispatchHeavyAnimationEvent();
onStart?.();
toggleExtraClass(container, `Transition-${name}`, !isBackwards);
@ -476,6 +483,8 @@ function performSlideOptimized(
onStop?.();
dispatchHeavyAnimationStop();
isAnimatingRef.current = false;
cleanup();
});
});

View File

@ -56,7 +56,7 @@ type AnimateNumberProps<T extends number | number[]> = {
duration: number;
onUpdate: (value: T) => void;
timing?: TimingFn;
onEnd?: () => void;
onEnd?: (isCanceled?: boolean) => void;
};
export const timingFunctions = {
@ -87,13 +87,15 @@ export function animateNumber<T extends number | number[]>({
to,
}: AnimateNumberProps<T>) {
const t0 = Date.now();
let canceled = false;
let isCanceled = false;
animateInstantly(() => {
if (canceled) return false;
if (isCanceled) return false;
const t1 = Date.now();
let t = (t1 - t0) / duration;
if (t > 1) t = 1;
const t = Math.min((t1 - t0) / duration, 1);
const progress = timing(t);
if (typeof from === 'number' && typeof to === 'number') {
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));
onUpdate(result as T);
}
if (t === 1 && onEnd) onEnd();
if (t === 1) {
onEnd?.();
}
return t < 1;
}, requestMeasure);
return () => {
canceled = true;
if (onEnd) onEnd();
isCanceled = true;
onEnd?.(true);
};
}

View File

@ -38,11 +38,10 @@ export function logUnequalProps(currentProps: AnyLiteral, newProps: AnyLiteral,
// eslint-disable-next-line no-console
console.log(msg);
for (const prop of currentKeys) {
currentKeys.forEach((prop) => {
if (currentProps[prop] !== newProps[prop]) {
// eslint-disable-next-line no-console
console.log(debugKey, prop, ':', currentProps[prop], '=>', newProps[prop]);
}
}
});
}

View File

@ -14,6 +14,7 @@ let isSwipeActive = false;
let swipeOffsets: MoveOffsets | undefined;
let onDrag: ((offsets: MoveOffsets) => void) | undefined;
let onRelease: ((onCancel: NoneToVoidFunction) => void) | undefined;
let cancelCurrentReleaseAnimation: NoneToVoidFunction | undefined;
export function captureControlledSwipe(
element: HTMLElement, options: {
@ -70,6 +71,8 @@ export function allowSwipeControlForTransition(
nextSlide: HTMLElement,
onCancelForTransition: NoneToVoidFunction,
) {
cancelCurrentReleaseAnimation?.();
if (!isSwipeActive) return;
const targetPosition = extractAnimationEndPosition(currentSlide);
@ -108,7 +111,7 @@ export function allowSwipeControlForTransition(
};
onRelease = (onCancelForClient: NoneToVoidFunction) => {
const isCanceled = currentDirection === -1;
const isRevertSwipe = currentDirection === -1;
function cleanup() {
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
waitForAnimationEnd(currentSlide, cleanup);
}
animateNumber({
cancelCurrentReleaseAnimation = animateNumber({
from: progress,
to: isCanceled ? 0 : 1,
to: isRevertSwipe ? 0 : 1,
duration: INERTIA_DURATION,
timing: INERTIA_EASING,
onUpdate(releaseProgress) {
updateAnimationProgress([currentSlide, nextSlide], releaseProgress);
},
onEnd() {
if (isCanceled) {
onEnd(isCanceled = false) {
cancelCurrentReleaseAnimation = undefined;
if (isCanceled || isRevertSwipe) {
cleanup();
onCancelForTransition();
onCancelForClient();