Removed unnessary context.restore() calls a using cached selectedElements in the interactive canvas

This commit is contained in:
Marcel Mraz 2023-08-06 21:46:39 +02:00
parent 58dcafe8f6
commit e58e9ec6d5
5 changed files with 35 additions and 43 deletions

View File

@ -1100,7 +1100,7 @@ class App extends React.Component<AppProps, AppState> {
}; };
public render() { public render() {
const selectedElement = this.scene.getSelectedElements(this.state); const selectedElements = this.scene.getSelectedElements(this.state);
const { renderTopRightUI, renderCustomStats } = this.props; const { renderTopRightUI, renderCustomStats } = this.props;
const versionNonce = this.scene.getVersionNonce(); const versionNonce = this.scene.getVersionNonce();
@ -1180,12 +1180,12 @@ class App extends React.Component<AppProps, AppState> {
<div className="excalidraw-textEditorContainer" /> <div className="excalidraw-textEditorContainer" />
<div className="excalidraw-contextMenuContainer" /> <div className="excalidraw-contextMenuContainer" />
<div className="excalidraw-eye-dropper-container" /> <div className="excalidraw-eye-dropper-container" />
{selectedElement.length === 1 && {selectedElements.length === 1 &&
!this.state.contextMenu && !this.state.contextMenu &&
this.state.showHyperlinkPopup && ( this.state.showHyperlinkPopup && (
<Hyperlink <Hyperlink
key={selectedElement[0].id} key={selectedElements[0].id}
element={selectedElement[0]} element={selectedElements[0]}
setAppState={this.setAppState} setAppState={this.setAppState}
onLinkOpen={this.props.onLinkOpen} onLinkOpen={this.props.onLinkOpen}
setToast={this.setToast} setToast={this.setToast}
@ -1229,6 +1229,7 @@ class App extends React.Component<AppProps, AppState> {
canvas={this.interactiveCanvas} canvas={this.interactiveCanvas}
elements={canvasElements} elements={canvasElements}
visibleElements={visibleElements} visibleElements={visibleElements}
selectedElements={selectedElements}
versionNonce={versionNonce} versionNonce={versionNonce}
selectionNonce={ selectionNonce={
this.state.selectionElement?.versionNonce this.state.selectionElement?.versionNonce

View File

@ -19,6 +19,7 @@ type InteractiveCanvasProps = {
canvas: HTMLCanvasElement | null; canvas: HTMLCanvasElement | null;
elements: readonly NonDeletedExcalidrawElement[]; elements: readonly NonDeletedExcalidrawElement[];
visibleElements: readonly NonDeletedExcalidrawElement[]; visibleElements: readonly NonDeletedExcalidrawElement[];
selectedElements: readonly NonDeletedExcalidrawElement[];
versionNonce: number | undefined; versionNonce: number | undefined;
selectionNonce: number | undefined; selectionNonce: number | undefined;
scale: number; scale: number;
@ -92,6 +93,7 @@ const InteractiveCanvas = (props: InteractiveCanvasProps) => {
canvas: props.canvas, canvas: props.canvas,
elements: props.elements, elements: props.elements,
visibleElements: props.visibleElements, visibleElements: props.visibleElements,
selectedElements: props.selectedElements,
scale: window.devicePixelRatio, scale: window.devicePixelRatio,
appState: props.appState, appState: props.appState,
renderConfig: { renderConfig: {
@ -179,7 +181,8 @@ const areEqual = (
// even if versionNonce didn't change (e.g. we filter elements out based // even if versionNonce didn't change (e.g. we filter elements out based
// on appState) // on appState)
prevProps.elements !== nextProps.elements || prevProps.elements !== nextProps.elements ||
prevProps.visibleElements !== nextProps.visibleElements prevProps.visibleElements !== nextProps.visibleElements ||
prevProps.selectedElements !== nextProps.selectedElements
) { ) {
return false; return false;
} }

View File

@ -276,7 +276,7 @@ export const getTransformHandles = (
}; };
export const shouldShowBoundingBox = ( export const shouldShowBoundingBox = (
elements: NonDeletedExcalidrawElement[], elements: readonly NonDeletedExcalidrawElement[],
appState: InteractiveCanvasAppState, appState: InteractiveCanvasAppState,
) => { ) => {
if (appState.editingLinearElement) { if (appState.editingLinearElement) {

View File

@ -38,7 +38,6 @@ import {
SCROLLBAR_COLOR, SCROLLBAR_COLOR,
SCROLLBAR_WIDTH, SCROLLBAR_WIDTH,
} from "../scene/scrollbars"; } from "../scene/scrollbars";
import { getSelectedElements } from "../scene/selection";
import { import {
renderElement, renderElement,
@ -398,7 +397,6 @@ const getNormalizedCanvasDimensions = (
const bootstrapCanvas = ({ const bootstrapCanvas = ({
canvas, canvas,
scale, scale,
zoom,
normalizedWidth, normalizedWidth,
normalizedHeight, normalizedHeight,
theme, theme,
@ -407,7 +405,6 @@ const bootstrapCanvas = ({
}: { }: {
canvas: HTMLCanvasElement; canvas: HTMLCanvasElement;
scale: number; scale: number;
zoom: CommonCanvasAppState["zoom"];
normalizedWidth: number; normalizedWidth: number;
normalizedHeight: number; normalizedHeight: number;
theme?: CommonCanvasAppState["theme"]; theme?: CommonCanvasAppState["theme"];
@ -417,7 +414,6 @@ const bootstrapCanvas = ({
const context = canvas.getContext("2d")!; const context = canvas.getContext("2d")!;
context.setTransform(1, 0, 0, 1, 0, 0); context.setTransform(1, 0, 0, 1, 0, 0);
context.save();
context.scale(scale, scale); context.scale(scale, scale);
if (isExporting && theme === "dark") { if (isExporting && theme === "dark") {
@ -442,10 +438,6 @@ const bootstrapCanvas = ({
context.clearRect(0, 0, normalizedWidth, normalizedHeight); context.clearRect(0, 0, normalizedWidth, normalizedHeight);
} }
// Apply zoom
context.save();
context.scale(zoom.value, zoom.value);
return context; return context;
}; };
@ -453,6 +445,7 @@ const _renderInteractiveScene = ({
canvas, canvas,
elements, elements,
visibleElements, visibleElements,
selectedElements,
scale, scale,
appState, appState,
renderConfig, renderConfig,
@ -471,9 +464,12 @@ const _renderInteractiveScene = ({
scale, scale,
normalizedWidth, normalizedWidth,
normalizedHeight, normalizedHeight,
zoom: appState.zoom,
}); });
// Apply zoom
context.save();
context.scale(appState.zoom.value, appState.zoom.value);
let editingLinearElement: NonDeleted<ExcalidrawLinearElement> | undefined = let editingLinearElement: NonDeleted<ExcalidrawLinearElement> | undefined =
undefined; undefined;
@ -517,8 +513,7 @@ const _renderInteractiveScene = ({
renderElementsBoxHighlight(context, appState, appState.elementsToHighlight); renderElementsBoxHighlight(context, appState, appState.elementsToHighlight);
} }
const locallySelectedElements = getSelectedElements(elements, appState); const isFrameSelected = selectedElements.some((element) =>
const isFrameSelected = locallySelectedElements.some((element) =>
isFrameElement(element), isFrameElement(element),
); );
@ -526,13 +521,13 @@ const _renderInteractiveScene = ({
// ShapeCache returns empty hence making sure that we get the // ShapeCache returns empty hence making sure that we get the
// correct element from visible elements // correct element from visible elements
if ( if (
locallySelectedElements.length === 1 && selectedElements.length === 1 &&
appState.editingLinearElement?.elementId === locallySelectedElements[0].id appState.editingLinearElement?.elementId === selectedElements[0].id
) { ) {
renderLinearPointHandles( renderLinearPointHandles(
context, context,
appState, appState,
locallySelectedElements[0] as NonDeleted<ExcalidrawLinearElement>, selectedElements[0] as NonDeleted<ExcalidrawLinearElement>,
); );
} }
@ -544,32 +539,27 @@ const _renderInteractiveScene = ({
} }
// Paint selected elements // Paint selected elements
if (!appState.multiElement && !appState.editingLinearElement) { if (!appState.multiElement && !appState.editingLinearElement) {
const showBoundingBox = shouldShowBoundingBox( const showBoundingBox = shouldShowBoundingBox(selectedElements, appState);
locallySelectedElements,
appState,
);
const isSingleLinearElementSelected = const isSingleLinearElementSelected =
locallySelectedElements.length === 1 && selectedElements.length === 1 && isLinearElement(selectedElements[0]);
isLinearElement(locallySelectedElements[0]);
// render selected linear element points // render selected linear element points
if ( if (
isSingleLinearElementSelected && isSingleLinearElementSelected &&
appState.selectedLinearElement?.elementId === appState.selectedLinearElement?.elementId === selectedElements[0].id &&
locallySelectedElements[0].id && !selectedElements[0].locked
!locallySelectedElements[0].locked
) { ) {
renderLinearPointHandles( renderLinearPointHandles(
context, context,
appState, appState,
locallySelectedElements[0] as ExcalidrawLinearElement, selectedElements[0] as ExcalidrawLinearElement,
); );
} }
const selectionColor = renderConfig.selectionColor || oc.black; const selectionColor = renderConfig.selectionColor || oc.black;
if (showBoundingBox) { if (showBoundingBox) {
// Optimisation for finding quickly relevant element ids // Optimisation for finding quickly relevant element ids
const locallySelectedIds = locallySelectedElements.reduce( const locallySelectedIds = selectedElements.reduce(
(acc: Record<string, boolean>, element) => { (acc: Record<string, boolean>, element) => {
acc[element.id] = true; acc[element.id] = true;
return acc; return acc;
@ -671,10 +661,10 @@ const _renderInteractiveScene = ({
context.save(); context.save();
context.translate(appState.scrollX, appState.scrollY); context.translate(appState.scrollX, appState.scrollY);
if (locallySelectedElements.length === 1) { if (selectedElements.length === 1) {
context.fillStyle = oc.white; context.fillStyle = oc.white;
const transformHandles = getTransformHandles( const transformHandles = getTransformHandles(
locallySelectedElements[0], selectedElements[0],
appState.zoom, appState.zoom,
"mouse", // when we render we don't know which pointer type so use mouse "mouse", // when we render we don't know which pointer type so use mouse
); );
@ -684,13 +674,13 @@ const _renderInteractiveScene = ({
renderConfig, renderConfig,
appState, appState,
transformHandles, transformHandles,
locallySelectedElements[0].angle, selectedElements[0].angle,
); );
} }
} else if (locallySelectedElements.length > 1 && !appState.isRotating) { } else if (selectedElements.length > 1 && !appState.isRotating) {
const dashedLinePadding = (DEFAULT_SPACING * 2) / appState.zoom.value; const dashedLinePadding = (DEFAULT_SPACING * 2) / appState.zoom.value;
context.fillStyle = oc.white; context.fillStyle = oc.white;
const [x1, y1, x2, y2] = getCommonBounds(locallySelectedElements); const [x1, y1, x2, y2] = getCommonBounds(selectedElements);
const initialLineDash = context.getLineDash(); const initialLineDash = context.getLineDash();
context.setLineDash([2 / appState.zoom.value]); context.setLineDash([2 / appState.zoom.value]);
const lineWidth = context.lineWidth; const lineWidth = context.lineWidth;
@ -717,7 +707,7 @@ const _renderInteractiveScene = ({
? OMIT_SIDES_FOR_FRAME ? OMIT_SIDES_FOR_FRAME
: OMIT_SIDES_FOR_MULTIPLE_ELEMENTS, : OMIT_SIDES_FOR_MULTIPLE_ELEMENTS,
); );
if (locallySelectedElements.some((element) => !element.locked)) { if (selectedElements.some((element) => !element.locked)) {
renderTransformHandles( renderTransformHandles(
context, context,
renderConfig, renderConfig,
@ -899,9 +889,6 @@ const _renderInteractiveScene = ({
context.restore(); context.restore();
} }
context.restore();
context.restore();
return { return {
scrollBars, scrollBars,
atLeastOneVisibleElement: visibleElements.length > 0, atLeastOneVisibleElement: visibleElements.length > 0,
@ -934,12 +921,14 @@ const _renderStaticScene = ({
scale, scale,
normalizedWidth, normalizedWidth,
normalizedHeight, normalizedHeight,
zoom: appState.zoom,
theme: appState.theme, theme: appState.theme,
isExporting, isExporting,
viewBackgroundColor: appState.viewBackgroundColor, viewBackgroundColor: appState.viewBackgroundColor,
}); });
// Apply zoom
context.scale(appState.zoom.value, appState.zoom.value);
// Grid // Grid
if (renderGrid && appState.gridSize) { if (renderGrid && appState.gridSize) {
strokeGrid( strokeGrid(
@ -1057,8 +1046,6 @@ const _renderStaticScene = ({
console.error(error); console.error(error);
} }
}); });
context.restore();
}; };
export const renderInteractiveScene = < export const renderInteractiveScene = <

View File

@ -53,6 +53,7 @@ export type InteractiveSceneRenderConfig = {
canvas: HTMLCanvasElement | null; canvas: HTMLCanvasElement | null;
elements: readonly NonDeletedExcalidrawElement[]; elements: readonly NonDeletedExcalidrawElement[];
visibleElements: readonly NonDeletedExcalidrawElement[]; visibleElements: readonly NonDeletedExcalidrawElement[];
selectedElements: readonly NonDeletedExcalidrawElement[];
scale: number; scale: number;
appState: InteractiveCanvasAppState; appState: InteractiveCanvasAppState;
renderConfig: InteractiveCanvasRenderConfig; renderConfig: InteractiveCanvasRenderConfig;