mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
feat: add zoom lock and viewportZoomFactor
This commit is contained in:
parent
e8e391e465
commit
9562e4309f
2 changed files with 43 additions and 21 deletions
|
@ -223,6 +223,7 @@ import {
|
||||||
FrameNameBoundsCache,
|
FrameNameBoundsCache,
|
||||||
SidebarName,
|
SidebarName,
|
||||||
SidebarTabName,
|
SidebarTabName,
|
||||||
|
NormalizedZoomValue,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import {
|
import {
|
||||||
debounce,
|
debounce,
|
||||||
|
@ -7650,20 +7651,17 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
scrollConstraints,
|
scrollConstraints,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
zoom,
|
zoom: currentZoom,
|
||||||
cursorButton,
|
cursorButton,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
// Set the overscroll allowance percentage
|
|
||||||
const OVERSCROLL_ALLOWANCE_PERCENTAGE = 0.2;
|
|
||||||
|
|
||||||
if (!scrollConstraints || scrollConstraints.isAnimating) {
|
if (!scrollConstraints || scrollConstraints.isAnimating) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the state has changed since the last render
|
// Check if the state has changed since the last render
|
||||||
const stateUnchanged =
|
const stateUnchanged =
|
||||||
zoom.value === prevState.zoom.value &&
|
currentZoom.value === prevState.zoom.value &&
|
||||||
scrollX === prevState.scrollX &&
|
scrollX === prevState.scrollX &&
|
||||||
scrollY === prevState.scrollY &&
|
scrollY === prevState.scrollY &&
|
||||||
width === prevState.width &&
|
width === prevState.width &&
|
||||||
|
@ -7671,10 +7669,17 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
cursorButton === prevState.cursorButton;
|
cursorButton === prevState.cursorButton;
|
||||||
|
|
||||||
// If the state hasn't changed and scrollConstraints didn't just get defined, return null
|
// If the state hasn't changed and scrollConstraints didn't just get defined, return null
|
||||||
if (!scrollConstraints || (stateUnchanged && prevState.scrollConstraints)) {
|
if (stateUnchanged && prevState.scrollConstraints) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set the overscroll allowance percentage
|
||||||
|
const OVERSCROLL_ALLOWANCE_PERCENTAGE = 0.2;
|
||||||
|
const lockZoom = scrollConstraints.opts?.lockZoom ?? false;
|
||||||
|
const viewportZoomFactor = scrollConstraints.opts?.viewportZoomFactor
|
||||||
|
? Math.min(1, Math.max(scrollConstraints.opts.viewportZoomFactor, 0.1))
|
||||||
|
: 0.9;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the zoom levels on which will constrained area fits the viewport for each axis
|
* 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.
|
* @returns The zoom levels for the X and Y axes.
|
||||||
|
@ -7684,20 +7689,21 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
const scrollableHeight = scrollConstraints.height;
|
const scrollableHeight = scrollConstraints.height;
|
||||||
const zoomLevelX = width / scrollableWidth;
|
const zoomLevelX = width / scrollableWidth;
|
||||||
const zoomLevelY = height / scrollableHeight;
|
const zoomLevelY = height / scrollableHeight;
|
||||||
return { zoomLevelX, zoomLevelY };
|
const maxZoomLevel = lockZoom
|
||||||
|
? Math.min(zoomLevelX, zoomLevelY) * viewportZoomFactor
|
||||||
|
: null;
|
||||||
|
return { zoomLevelX, zoomLevelY, maxZoomLevel };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the center position of the constrained scroll area.
|
* Calculates the center position of the constrained scroll area.
|
||||||
* @returns The X and Y coordinates of the center position.
|
* @returns The X and Y coordinates of the center position.
|
||||||
*/
|
*/
|
||||||
const calculateConstrainedScrollCenter = () => {
|
const calculateConstrainedScrollCenter = (zoom: number) => {
|
||||||
const constrainedScrollCenterX =
|
const constrainedScrollCenterX =
|
||||||
scrollConstraints.x +
|
scrollConstraints.x + (scrollConstraints.width - width / zoom) / -2;
|
||||||
(scrollConstraints.width - width / zoom.value) / -2;
|
|
||||||
const constrainedScrollCenterY =
|
const constrainedScrollCenterY =
|
||||||
scrollConstraints.y +
|
scrollConstraints.y + (scrollConstraints.height - height / zoom) / -2;
|
||||||
(scrollConstraints.height - height / zoom.value) / -2;
|
|
||||||
return { constrainedScrollCenterX, constrainedScrollCenterY };
|
return { constrainedScrollCenterX, constrainedScrollCenterY };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7728,6 +7734,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
overscrollAllowanceY: number,
|
overscrollAllowanceY: number,
|
||||||
constrainedScrollCenterX: number,
|
constrainedScrollCenterX: number,
|
||||||
constrainedScrollCenterY: number,
|
constrainedScrollCenterY: number,
|
||||||
|
zoom: number,
|
||||||
) => {
|
) => {
|
||||||
let maxScrollX;
|
let maxScrollX;
|
||||||
let minScrollX;
|
let minScrollX;
|
||||||
|
@ -7744,13 +7751,13 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
minScrollX =
|
minScrollX =
|
||||||
scrollConstraints.x -
|
scrollConstraints.x -
|
||||||
scrollConstraints.width +
|
scrollConstraints.width +
|
||||||
width / zoom.value -
|
width / zoom -
|
||||||
overscrollAllowanceX;
|
overscrollAllowanceX;
|
||||||
maxScrollY = scrollConstraints.y + overscrollAllowanceY;
|
maxScrollY = scrollConstraints.y + overscrollAllowanceY;
|
||||||
minScrollY =
|
minScrollY =
|
||||||
scrollConstraints.y -
|
scrollConstraints.y -
|
||||||
scrollConstraints.height +
|
scrollConstraints.height +
|
||||||
height / zoom.value -
|
height / zoom -
|
||||||
overscrollAllowanceY;
|
overscrollAllowanceY;
|
||||||
} else if (cursorButton !== "down" && shouldAdjustForCenteredView) {
|
} else if (cursorButton !== "down" && shouldAdjustForCenteredView) {
|
||||||
maxScrollX = constrainedScrollCenterX;
|
maxScrollX = constrainedScrollCenterX;
|
||||||
|
@ -7760,10 +7767,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
} else {
|
} else {
|
||||||
maxScrollX = scrollConstraints.x;
|
maxScrollX = scrollConstraints.x;
|
||||||
minScrollX =
|
minScrollX =
|
||||||
scrollConstraints.x - scrollConstraints.width + width / zoom.value;
|
scrollConstraints.x - scrollConstraints.width + width / zoom;
|
||||||
maxScrollY = scrollConstraints.y;
|
maxScrollY = scrollConstraints.y;
|
||||||
minScrollY =
|
minScrollY =
|
||||||
scrollConstraints.y - scrollConstraints.height + height / zoom.value;
|
scrollConstraints.y - scrollConstraints.height + height / zoom;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { maxScrollX, minScrollX, maxScrollY, minScrollY };
|
return { maxScrollX, minScrollX, maxScrollY, minScrollY };
|
||||||
|
@ -7816,7 +7823,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
scrollY + height >
|
scrollY + height >
|
||||||
scrollConstraints.y + scrollConstraints.height) &&
|
scrollConstraints.y + scrollConstraints.height) &&
|
||||||
cursorButton !== "down" &&
|
cursorButton !== "down" &&
|
||||||
zoom.value === prevState.zoom.value
|
currentZoom.value === prevState.zoom.value
|
||||||
) {
|
) {
|
||||||
this.setState({
|
this.setState({
|
||||||
scrollConstraints: { ...scrollConstraints, isAnimating: true },
|
scrollConstraints: { ...scrollConstraints, isAnimating: true },
|
||||||
|
@ -7849,7 +7856,14 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
const constrainedState = {
|
const constrainedState = {
|
||||||
scrollX: constrainedScrollX,
|
scrollX: constrainedScrollX,
|
||||||
scrollY: constrainedScrollY,
|
scrollY: constrainedScrollY,
|
||||||
zoom,
|
zoom: {
|
||||||
|
value: maxZoomLevel
|
||||||
|
? (Math.max(
|
||||||
|
currentZoom.value,
|
||||||
|
maxZoomLevel,
|
||||||
|
) as NormalizedZoomValue)
|
||||||
|
: currentZoom.value,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setState(constrainedState);
|
this.setState(constrainedState);
|
||||||
|
@ -7860,13 +7874,16 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Compute the constrained scroll values.
|
// Compute the constrained scroll values.
|
||||||
const { zoomLevelX, zoomLevelY } = calculateZoomLevel();
|
const { zoomLevelX, zoomLevelY, maxZoomLevel } = calculateZoomLevel();
|
||||||
|
const zoom = maxZoomLevel
|
||||||
|
? Math.max(maxZoomLevel, currentZoom.value)
|
||||||
|
: currentZoom.value;
|
||||||
const { constrainedScrollCenterX, constrainedScrollCenterY } =
|
const { constrainedScrollCenterX, constrainedScrollCenterY } =
|
||||||
calculateConstrainedScrollCenter();
|
calculateConstrainedScrollCenter(zoom);
|
||||||
const { overscrollAllowanceX, overscrollAllowanceY } =
|
const { overscrollAllowanceX, overscrollAllowanceY } =
|
||||||
calculateOverscrollAllowance();
|
calculateOverscrollAllowance();
|
||||||
const shouldAdjustForCenteredView =
|
const shouldAdjustForCenteredView =
|
||||||
zoom.value <= zoomLevelX || zoom.value <= zoomLevelY;
|
zoom <= zoomLevelX || zoom <= zoomLevelY;
|
||||||
const { maxScrollX, minScrollX, maxScrollY, minScrollY } =
|
const { maxScrollX, minScrollX, maxScrollY, minScrollY } =
|
||||||
calculateMinMaxScrollValues(
|
calculateMinMaxScrollValues(
|
||||||
shouldAdjustForCenteredView,
|
shouldAdjustForCenteredView,
|
||||||
|
@ -7874,6 +7891,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||||
overscrollAllowanceY,
|
overscrollAllowanceY,
|
||||||
constrainedScrollCenterX,
|
constrainedScrollCenterX,
|
||||||
constrainedScrollCenterY,
|
constrainedScrollCenterY,
|
||||||
|
zoom,
|
||||||
);
|
);
|
||||||
const { constrainedScrollX, constrainedScrollY } = constrainScrollValues(
|
const { constrainedScrollX, constrainedScrollY } = constrainScrollValues(
|
||||||
maxScrollX,
|
maxScrollX,
|
||||||
|
|
|
@ -229,6 +229,10 @@ export type AppState = {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
isAnimating?: boolean;
|
isAnimating?: boolean;
|
||||||
|
opts?: {
|
||||||
|
viewportZoomFactor?: number;
|
||||||
|
lockZoom?: boolean;
|
||||||
|
};
|
||||||
} | null;
|
} | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue