refactor: split constrainScroll into smaller functions

This commit is contained in:
Arnošt Pleskot 2023-07-09 23:50:59 +02:00
parent 92be92071a
commit e8e391e465
No known key found for this signature in database

View File

@ -7675,154 +7675,214 @@ class App extends React.Component<AppProps, AppState> {
return null;
}
// Calculate the zoom level on which will constrained area fit the viewport for each axis
const scrollableWidth = scrollConstraints.width;
const scrollableHeight = scrollConstraints.height;
const zoomLevelX = width / scrollableWidth;
const zoomLevelY = height / scrollableHeight;
/**
* Calculate the zoom levels on which will constrained area fits the viewport for each axis
* @returns The zoom levels for the X and Y axes.
*/
const calculateZoomLevel = () => {
const scrollableWidth = scrollConstraints.width;
const scrollableHeight = scrollConstraints.height;
const zoomLevelX = width / scrollableWidth;
const zoomLevelY = height / scrollableHeight;
return { zoomLevelX, zoomLevelY };
};
// Default scroll and zoom values
let constrainedScrollX = scrollX;
let constrainedScrollY = scrollY;
/**
* Calculates the center position of the constrained scroll area.
* @returns The X and Y coordinates of the center position.
*/
const calculateConstrainedScrollCenter = () => {
const constrainedScrollCenterX =
scrollConstraints.x +
(scrollConstraints.width - width / zoom.value) / -2;
const constrainedScrollCenterY =
scrollConstraints.y +
(scrollConstraints.height - height / zoom.value) / -2;
return { constrainedScrollCenterX, constrainedScrollCenterY };
};
// Calculate the overscroll allowance for each axis
const overscrollAllowanceX =
OVERSCROLL_ALLOWANCE_PERCENTAGE * scrollableWidth;
const overscrollAllowanceY =
OVERSCROLL_ALLOWANCE_PERCENTAGE * scrollableHeight;
/**
* Calculates the overscroll allowance values for the constrained area.
* @returns The overscroll allowance values for the X and Y axes.
*/
const calculateOverscrollAllowance = () => {
const overscrollAllowanceX =
OVERSCROLL_ALLOWANCE_PERCENTAGE * scrollConstraints.width;
const overscrollAllowanceY =
OVERSCROLL_ALLOWANCE_PERCENTAGE * scrollConstraints.height;
return { overscrollAllowanceX, overscrollAllowanceY };
};
// When we are zoomed out enough to contain constrained area in the viewport we will center the view
const shouldAdjustForCenteredView =
zoom.value <= zoomLevelX || zoom.value <= zoomLevelY;
// When viewport is smaller than the scrollable area, user can pan freely within the constrained area,
// otherwilse the viewport is centered to the center of the scrollable area
let maxScrollX;
let minScrollX;
let maxScrollY;
let minScrollY;
// Get center of scrollable area
const constrainedScrollCenterX =
scrollConstraints.x + (scrollableWidth - width / zoom.value) / -2;
const constrainedScrollCenterY =
scrollConstraints.y + (scrollableHeight - height / zoom.value) / -2;
// We're using a `switch(true)` construction here to handle a set of conditions
// that can't be easily grouped into a regular switch statement.
// Each case represents a unique combination of cursorButton state and
// whether or not we should adjust for a centered view (constrained area is smaller than viewport).
switch (true) {
case cursorButton === "down" && shouldAdjustForCenteredView:
// case when cursor button is down and we should adjust for centered view
/**
* Calculates the minimum and maximum scroll values based on the current state.
* @param shouldAdjustForCenteredView - Whether the view should be adjusted for centered view - when constrained area fits the viewport.
* @param overscrollAllowanceX - The overscroll allowance value for the X axis.
* @param overscrollAllowanceY - The overscroll allowance value for the Y axis.
* @param constrainedScrollCenterX - The X coordinate of the constrained scroll area center.
* @param constrainedScrollCenterY - The Y coordinate of the constrained scroll area center.
* @returns The minimum and maximum scroll values for the X and Y axes.
*/
const calculateMinMaxScrollValues = (
shouldAdjustForCenteredView: boolean,
overscrollAllowanceX: number,
overscrollAllowanceY: number,
constrainedScrollCenterX: number,
constrainedScrollCenterY: number,
) => {
let maxScrollX;
let minScrollX;
let maxScrollY;
let minScrollY;
if (cursorButton === "down" && shouldAdjustForCenteredView) {
maxScrollX = constrainedScrollCenterX + overscrollAllowanceX;
minScrollX = constrainedScrollCenterX - overscrollAllowanceX;
maxScrollY = constrainedScrollCenterY + overscrollAllowanceY;
minScrollY = constrainedScrollCenterY - overscrollAllowanceY;
break;
case cursorButton === "down" && !shouldAdjustForCenteredView:
// case when cursor button is down and we should not adjust for centered view
} else if (cursorButton === "down" && !shouldAdjustForCenteredView) {
maxScrollX = scrollConstraints.x + overscrollAllowanceX;
minScrollX =
scrollConstraints.x -
scrollableWidth +
scrollConstraints.width +
width / zoom.value -
overscrollAllowanceX;
maxScrollY = scrollConstraints.y + overscrollAllowanceY;
minScrollY =
scrollConstraints.y -
scrollableHeight +
scrollConstraints.height +
height / zoom.value -
overscrollAllowanceY;
break;
case cursorButton !== "down" && shouldAdjustForCenteredView:
// case when cursor button is not down and we should adjust for centered view
} else if (cursorButton !== "down" && shouldAdjustForCenteredView) {
maxScrollX = constrainedScrollCenterX;
minScrollX = constrainedScrollCenterX;
maxScrollY = constrainedScrollCenterY;
minScrollY = constrainedScrollCenterY;
break;
default:
// case when cursor button is not down and we should not adjust for centered view
} else {
maxScrollX = scrollConstraints.x;
minScrollX = scrollConstraints.x - scrollableWidth + width / zoom.value;
minScrollX =
scrollConstraints.x - scrollConstraints.width + width / zoom.value;
maxScrollY = scrollConstraints.y;
minScrollY =
scrollConstraints.y - scrollableHeight + height / zoom.value;
break;
}
// Constrain the scroll within the scroll constraints
constrainedScrollX = Math.min(maxScrollX, Math.max(scrollX, minScrollX));
constrainedScrollY = Math.min(maxScrollY, Math.max(scrollY, minScrollY));
// Check if the new state differs from the old state
// and if the state has changed, update the state and return the new state
const isStateChanged =
constrainedScrollX !== scrollX || constrainedScrollY !== scrollY;
if (isStateChanged) {
// Animate the scroll position when the cursor button is not down and scroll position is outside of the scroll constraints.
// We don't want to animate the scroll position when the user is dragging the canvas or zooiming in/out.
if (
(scrollX < scrollConstraints.x ||
scrollX + width > scrollConstraints.x + scrollConstraints.width ||
scrollY < scrollConstraints.y ||
scrollY + height > scrollConstraints.y + scrollConstraints.height) &&
cursorButton !== "down" &&
zoom.value === prevState.zoom.value
) {
this.setState({
scrollConstraints: { ...scrollConstraints, isAnimating: true },
});
easeToValuesRAF({
fromValues: { scrollX, scrollY },
toValues: {
scrollX: constrainedScrollX,
scrollY: constrainedScrollY,
},
onStep: ({ scrollX, scrollY }) => {
this.setState({
scrollX,
scrollY,
});
},
onStart: () => {
this.setState({
scrollConstraints: { ...scrollConstraints, isAnimating: true },
});
},
onEnd: () => {
this.setState({
scrollConstraints: { ...scrollConstraints, isAnimating: false },
});
},
});
return null;
scrollConstraints.y - scrollConstraints.height + height / zoom.value;
}
const constrainedState = {
scrollX: constrainedScrollX,
scrollY: constrainedScrollY,
zoom,
};
return { maxScrollX, minScrollX, maxScrollY, minScrollY };
};
this.setState(constrainedState);
return constrainedState;
}
/**
* Constrains the scroll values within the constrained area.
* @param maxScrollX - The maximum scroll value for the X axis.
* @param minScrollX - The minimum scroll value for the X axis.
* @param maxScrollY - The maximum scroll value for the Y axis.
* @param minScrollY - The minimum scroll value for the Y axis.
* @returns The constrained scroll values for the X and Y axes.
*/
const constrainScrollValues = (
maxScrollX: number,
minScrollX: number,
maxScrollY: number,
minScrollY: number,
) => {
const constrainedScrollX = Math.min(
maxScrollX,
Math.max(scrollX, minScrollX),
);
const constrainedScrollY = Math.min(
maxScrollY,
Math.max(scrollY, minScrollY),
);
return { constrainedScrollX, constrainedScrollY };
};
return null;
/**
* Handles the state change based on the constrained scroll values.
* Also handles the animation to the constrained area when the viewport is outside of constrained area.
* @param constrainedScrollX - The constrained scroll value for the X axis.
* @param constrainedScrollY - The constrained scroll value for the Y axis.
* @returns The constrained state if the state has changed, when needs to be passed into render function, otherwise null.
*/
const handleStateChange = (
constrainedScrollX: number,
constrainedScrollY: number,
) => {
const isStateChanged =
constrainedScrollX !== scrollX || constrainedScrollY !== scrollY;
if (isStateChanged) {
if (
(scrollX < scrollConstraints.x ||
scrollX + width > scrollConstraints.x + scrollConstraints.width ||
scrollY < scrollConstraints.y ||
scrollY + height >
scrollConstraints.y + scrollConstraints.height) &&
cursorButton !== "down" &&
zoom.value === prevState.zoom.value
) {
this.setState({
scrollConstraints: { ...scrollConstraints, isAnimating: true },
});
easeToValuesRAF({
fromValues: { scrollX, scrollY },
toValues: {
scrollX: constrainedScrollX,
scrollY: constrainedScrollY,
},
onStep: ({ scrollX, scrollY }) => {
this.setState({ scrollX, scrollY });
},
onStart: () => {
this.setState({
scrollConstraints: { ...scrollConstraints, isAnimating: true },
});
},
onEnd: () => {
this.setState({
scrollConstraints: { ...scrollConstraints, isAnimating: false },
});
},
});
return null;
}
const constrainedState = {
scrollX: constrainedScrollX,
scrollY: constrainedScrollY,
zoom,
};
this.setState(constrainedState);
return constrainedState;
}
return null;
};
// Compute the constrained scroll values.
const { zoomLevelX, zoomLevelY } = calculateZoomLevel();
const { constrainedScrollCenterX, constrainedScrollCenterY } =
calculateConstrainedScrollCenter();
const { overscrollAllowanceX, overscrollAllowanceY } =
calculateOverscrollAllowance();
const shouldAdjustForCenteredView =
zoom.value <= zoomLevelX || zoom.value <= zoomLevelY;
const { maxScrollX, minScrollX, maxScrollY, minScrollY } =
calculateMinMaxScrollValues(
shouldAdjustForCenteredView,
overscrollAllowanceX,
overscrollAllowanceY,
constrainedScrollCenterX,
constrainedScrollCenterY,
);
const { constrainedScrollX, constrainedScrollY } = constrainScrollValues(
maxScrollX,
minScrollX,
maxScrollY,
minScrollY,
);
return handleStateChange(constrainedScrollX, constrainedScrollY);
};
}