feat: animation depending on timeout

This commit is contained in:
Arnošt Pleskot 2023-09-13 14:54:39 +02:00
parent b99bf74c3d
commit dd9bde5ee7
No known key found for this signature in database
2 changed files with 126 additions and 76 deletions

View file

@ -357,8 +357,8 @@ import { ShapeCache } from "../scene/ShapeCache";
import {
calculateConstrainedScrollCenter,
constrainScrollState,
isViewportOutsideOfConstrainedArea,
} from "../scene/scrollConstraints";
import { cancelRender } from "../renderer/renderScene";
const AppContext = React.createContext<AppClassProperties>(null!);
const AppPropsContext = React.createContext<AppProps>(null!);
@ -428,6 +428,8 @@ let isDraggingScrollBar: boolean = false;
let currentScrollBars: ScrollBars = { horizontal: null, vertical: null };
let touchTimeout = 0;
let invalidateContextMenu = false;
let scrollConstraintsAnimationTimeout: ReturnType<typeof setTimeout> | null =
null;
/**
* Map of youtube embed video states
@ -2005,58 +2007,6 @@ class App extends React.Component<AppProps, AppState> {
this.files,
);
}
if (
this.state.scrollConstraints &&
isViewportOutsideOfConstrainedArea(this.state) &&
prevState.cursorButton === "down" &&
this.state.cursorButton !== "down" &&
!this.state.isLoading
) {
const state = constrainScrollState(this.state);
const cancel = easeToValuesRAF({
fromValues: {
scrollX: this.state.scrollX,
scrollY: this.state.scrollY,
zoom: this.state.zoom.value,
},
toValues: {
scrollX: state.scrollX,
scrollY: state.scrollY,
zoom: state.zoom.value,
},
interpolateValue: (from, to, progress, key) => {
// for zoom, use different easing
if (key === "zoom") {
return from * Math.pow(to / from, easeOut(progress));
}
// handle using default
return undefined;
},
onStep: ({ scrollX, scrollY, zoom }) => {
this.setState({
scrollX,
scrollY,
zoom: { value: getNormalizedZoom(zoom) },
});
},
onStart: () => {
this.setState({ shouldCacheIgnoreZoom: true });
},
onEnd: () => {
this.setState({ shouldCacheIgnoreZoom: false });
},
onCancel: () => {
this.setState({ shouldCacheIgnoreZoom: false });
},
duration: 500,
});
this.cancelInProgresAnimation = () => {
cancel();
this.cancelInProgresAnimation = null;
};
}
}
private renderInteractiveSceneCallback = ({
@ -2725,6 +2675,9 @@ class App extends React.Component<AppProps, AppState> {
stateUpdate,
) => {
this.cancelInProgresAnimation?.();
if (scrollConstraintsAnimationTimeout) {
clearTimeout(scrollConstraintsAnimationTimeout);
}
const partialNewState =
typeof stateUpdate === "function"
@ -2739,11 +2692,87 @@ class App extends React.Component<AppProps, AppState> {
const newState: AppState = {
...this.state,
...partialNewState,
...(this.state.scrollConstraints && {
// manually reset if setState in onCancel wasn't committed yet
shouldCacheIgnoreZoom: false,
scrollConstraints: {
...this.state.scrollConstraints,
isAnimating: false,
},
}),
};
const state = constrainScrollState(newState);
const { state, shouldAnimate, animateTo } = constrainScrollState(newState);
this.setState(state);
this.setState(state, () => {
if (shouldAnimate && animateTo) {
scrollConstraintsAnimationTimeout = setTimeout(() => {
const cancel = easeToValuesRAF({
fromValues: {
scrollX: newState.scrollX,
scrollY: newState.scrollY,
zoom: newState.zoom.value,
},
toValues: {
scrollX: animateTo.scrollX,
scrollY: animateTo.scrollY,
zoom: animateTo.zoom.value,
},
interpolateValue: (from, to, progress, key) => {
// for zoom, use different easing
if (key === "zoom") {
return from * Math.pow(to / from, easeOut(progress));
}
// handle using default
return undefined;
},
onStep: ({ scrollX, scrollY, zoom }) => {
this.setState({
scrollX,
scrollY,
zoom: { value: getNormalizedZoom(zoom) },
});
},
onStart: () => {
this.setState((inAnimationState) => ({
shouldCacheIgnoreZoom: true,
scrollConstraints: {
...inAnimationState.scrollConstraints!, // existance scrollConstraints is checked in test for shouldAnimate
isAnimating: true,
},
}));
cancelRender();
},
onEnd: () => {
this.setState((inAnimationState) => ({
shouldCacheIgnoreZoom: false,
scrollConstraints: {
...inAnimationState.scrollConstraints!,
isAnimating: false,
},
}));
},
onCancel: () => {
this.setState((inAnimationState) => {
return {
shouldCacheIgnoreZoom: false,
scrollConstraints: {
...inAnimationState.scrollConstraints!,
isAnimating: false,
},
};
});
},
duration: 200,
});
this.cancelInProgresAnimation = () => {
cancel();
this.cancelInProgresAnimation = null;
};
}, 200);
}
});
};
setToast = (

View file

@ -286,9 +286,9 @@ const constrainScrollValues = ({
* @param state - The application state containing scroll, zoom, and constraint information.
* @returns True if the viewport is outside the constrained area; otherwise undefined.
*/
export const isViewportOutsideOfConstrainedArea = (state: AppState) => {
const isViewportOutsideOfConstrainedArea = (state: AppState) => {
if (!state.scrollConstraints) {
return;
return false;
}
const { scrollX, scrollY, width, height, scrollConstraints } = state;
@ -307,19 +307,21 @@ export const isViewportOutsideOfConstrainedArea = (state: AppState) => {
* @param state - The original AppState with the current scroll position, dimensions, and constraints.
* @returns A new AppState object with scroll values constrained as per the defined constraints.
*/
export const constrainScrollState = (state: AppState): AppState => {
export const constrainScrollState = (
state: AppState,
): {
state: AppState;
shouldAnimate: boolean;
animateTo: {
scrollX: AppState["scrollX"];
scrollY: AppState["scrollY"];
zoom: AppState["zoom"];
} | null;
} => {
if (!state.scrollConstraints || state.scrollConstraints.isAnimating) {
return state;
return { state, shouldAnimate: false, animateTo: null };
}
const {
scrollX,
scrollY,
width,
height,
scrollConstraints,
zoom,
cursorButton,
} = state;
const { scrollX, scrollY, width, height, scrollConstraints, zoom } = state;
const constrainedValues = constrainScrollValues({
...calculateConstraints({
@ -327,22 +329,41 @@ export const constrainScrollState = (state: AppState): AppState => {
width,
height,
zoom,
cursorButton,
cursorButton: "down",
}),
scrollX,
scrollY,
});
const isStateChanged =
constrainedValues.scrollX !== scrollX ||
constrainedValues.scrollY !== scrollY;
const shouldAnimate = isViewportOutsideOfConstrainedArea(state);
if (isStateChanged) {
const animateTo = shouldAnimate
? constrainScrollValues({
...calculateConstraints({
scrollConstraints,
width,
height,
zoom,
cursorButton: "up",
}),
scrollX,
scrollY,
})
: null;
if (
constrainedValues.scrollX !== scrollX ||
constrainedValues.scrollY !== scrollY
) {
return {
state: {
...state,
...constrainedValues,
},
shouldAnimate,
animateTo,
};
}
return state;
return { state, shouldAnimate, animateTo };
};