diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx index e437ab072..9bba9672e 100644 --- a/packages/excalidraw/components/App.tsx +++ b/packages/excalidraw/components/App.tsx @@ -531,6 +531,7 @@ import type { Action, ActionResult } from "../actions/types"; import { constrainScrollState, calculateConstrainedScrollCenter, + areCanvasViewsClose, } from "../scene/scrollConstraints"; const AppContext = React.createContext(null!); @@ -2975,20 +2976,27 @@ class App extends React.Component { const newState = constrainScrollState(this.state, { allowOverscroll: false, }); + const fromValues = { + scrollX: this.state.scrollX, + scrollY: this.state.scrollY, + zoom: this.state.zoom.value, + }; + const toValues = { + scrollX: newState.scrollX, + scrollY: newState.scrollY, + zoom: newState.zoom.value, + }; + + if (areCanvasViewsClose(fromValues, toValues)) { + return; + } + + if (scrollConstraintsAnimationTimeout) { + clearTimeout(scrollConstraintsAnimationTimeout); + } scrollConstraintsAnimationTimeout = setTimeout(() => { this.cancelInProgressAnimation?.(); - const fromValues = { - scrollX: this.state.scrollX, - scrollY: this.state.scrollY, - zoom: this.state.zoom.value, - }; - const toValues = { - scrollX: newState.scrollX, - scrollY: newState.scrollY, - zoom: newState.zoom.value, - }; - this.animateToConstrainedArea(fromValues, toValues); }, 200); } diff --git a/packages/excalidraw/scene/scrollConstraints.ts b/packages/excalidraw/scene/scrollConstraints.ts index 32fafb3bb..49878a509 100644 --- a/packages/excalidraw/scene/scrollConstraints.ts +++ b/packages/excalidraw/scene/scrollConstraints.ts @@ -1,5 +1,9 @@ import { isShallowEqual } from "@excalidraw/common"; -import { AppState, ScrollConstraints } from "../types"; +import { + AnimateTranslateCanvasValues, + AppState, + ScrollConstraints, +} from "../types"; import { getNormalizedZoom } from "./normalize"; /** @@ -18,14 +22,13 @@ import { getNormalizedZoom } from "./normalize"; * * const { scrollX, scrollY, zoom } = this.calculateConstrainedScrollCenter(scrollConstraints, { scrollX, scrollY }); */ + +type CanvasTranslate = Pick; + export const calculateConstrainedScrollCenter = ( state: AppState, { scrollX, scrollY }: Pick, -): { - scrollX: AppState["scrollX"]; - scrollY: AppState["scrollY"]; - zoom: AppState["zoom"]; -} => { +): CanvasTranslate => { const { width, height, @@ -352,7 +355,7 @@ const constrainScrollValues = ({ maxScrollY: number; minScrollY: number; constrainedZoom: AppState["zoom"]; -}) => { +}): CanvasTranslate => { const constrainedScrollX = Math.min( maxScrollX, Math.max(scrollX, minScrollX), @@ -389,7 +392,7 @@ const alignScrollConstraints = ( * Determines whether the current viewport is outside the constrained area defined in the AppState. * * @param state - The application state containing scroll, zoom, and constraint information. - * @returns True if the viewport is outside the constrained area; otherwise undefined. + * @returns True if the viewport is outside the constrained area, false otherwise. */ const isViewportOutsideOfConstrainedArea = (state: AppState) => { if (!state.scrollConstraints) { @@ -521,3 +524,15 @@ export const constrainScrollState = ( ...constrainedValues, }; }; + +export const areCanvasViewsClose = ( + from: AnimateTranslateCanvasValues, + to: AnimateTranslateCanvasValues, +): boolean => { + const threshold = 0.1; // Adjust based on your needs + return ( + Math.abs(from.scrollX - to.scrollX) < threshold && + Math.abs(from.scrollY - to.scrollY) < threshold && + Math.abs(from.zoom - to.zoom) < threshold + ); +};