mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
perf: make eraser great again (#9352)
* perf: make eraser great again * lint * refactor and improve perf * lint
This commit is contained in:
parent
6fe7de8020
commit
58f7d33d80
5 changed files with 259 additions and 120 deletions
|
@ -454,7 +454,6 @@ import {
|
|||
import { Emitter } from "../emitter";
|
||||
import { ElementCanvasButtons } from "../components/ElementCanvasButtons";
|
||||
import { Store, CaptureUpdateAction } from "../store";
|
||||
import { AnimatedTrail } from "../animated-trail";
|
||||
import { LaserTrails } from "../laser-trails";
|
||||
import { withBatchedUpdates, withBatchedUpdatesThrottled } from "../reactUtils";
|
||||
import { textWysiwyg } from "../wysiwyg/textWysiwyg";
|
||||
|
@ -464,6 +463,8 @@ import { isMaybeMermaidDefinition } from "../mermaid";
|
|||
|
||||
import { LassoTrail } from "../lasso";
|
||||
|
||||
import { EraserTrail } from "../eraser";
|
||||
|
||||
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
|
||||
import BraveMeasureTextError from "./BraveMeasureTextError";
|
||||
import { ContextMenu, CONTEXT_MENU_SEPARATOR } from "./ContextMenu";
|
||||
|
@ -675,26 +676,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
animationFrameHandler = new AnimationFrameHandler();
|
||||
|
||||
laserTrails = new LaserTrails(this.animationFrameHandler, this);
|
||||
eraserTrail = new AnimatedTrail(this.animationFrameHandler, this, {
|
||||
streamline: 0.2,
|
||||
size: 5,
|
||||
keepHead: true,
|
||||
sizeMapping: (c) => {
|
||||
const DECAY_TIME = 200;
|
||||
const DECAY_LENGTH = 10;
|
||||
const t = Math.max(0, 1 - (performance.now() - c.pressure) / DECAY_TIME);
|
||||
const l =
|
||||
(DECAY_LENGTH -
|
||||
Math.min(DECAY_LENGTH, c.totalLength - c.currentIndex)) /
|
||||
DECAY_LENGTH;
|
||||
|
||||
return Math.min(easeOut(l), easeOut(t));
|
||||
},
|
||||
fill: () =>
|
||||
this.state.theme === THEME.LIGHT
|
||||
? "rgba(0, 0, 0, 0.2)"
|
||||
: "rgba(255, 255, 255, 0.2)",
|
||||
});
|
||||
eraserTrail = new EraserTrail(this.animationFrameHandler, this);
|
||||
lassoTrail = new LassoTrail(this.animationFrameHandler, this);
|
||||
|
||||
onChangeEmitter = new Emitter<
|
||||
|
@ -1676,8 +1658,8 @@ class App extends React.Component<AppProps, AppState> {
|
|||
<SVGLayer
|
||||
trails={[
|
||||
this.laserTrails,
|
||||
this.eraserTrail,
|
||||
this.lassoTrail,
|
||||
this.eraserTrail,
|
||||
]}
|
||||
/>
|
||||
{selectedElements.length === 1 &&
|
||||
|
@ -5163,7 +5145,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
return elements;
|
||||
}
|
||||
|
||||
private getElementHitThreshold() {
|
||||
getElementHitThreshold() {
|
||||
return DEFAULT_COLLISION_THRESHOLD / this.state.zoom.value;
|
||||
}
|
||||
|
||||
|
@ -6219,101 +6201,16 @@ class App extends React.Component<AppProps, AppState> {
|
|||
|
||||
private handleEraser = (
|
||||
event: PointerEvent,
|
||||
pointerDownState: PointerDownState,
|
||||
scenePointer: { x: number; y: number },
|
||||
) => {
|
||||
this.eraserTrail.addPointToPath(scenePointer.x, scenePointer.y);
|
||||
|
||||
let didChange = false;
|
||||
|
||||
const processedGroups = new Set<ExcalidrawElement["id"]>();
|
||||
const nonDeletedElements = this.scene.getNonDeletedElements();
|
||||
|
||||
const processElements = (elements: ExcalidrawElement[]) => {
|
||||
for (const element of elements) {
|
||||
if (element.locked) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.altKey) {
|
||||
if (this.elementsPendingErasure.delete(element.id)) {
|
||||
didChange = true;
|
||||
}
|
||||
} else if (!this.elementsPendingErasure.has(element.id)) {
|
||||
didChange = true;
|
||||
this.elementsPendingErasure.add(element.id);
|
||||
}
|
||||
|
||||
// (un)erase groups atomically
|
||||
if (didChange && element.groupIds?.length) {
|
||||
const shallowestGroupId = element.groupIds.at(-1)!;
|
||||
if (!processedGroups.has(shallowestGroupId)) {
|
||||
processedGroups.add(shallowestGroupId);
|
||||
const elems = getElementsInGroup(
|
||||
nonDeletedElements,
|
||||
shallowestGroupId,
|
||||
);
|
||||
for (const elem of elems) {
|
||||
if (event.altKey) {
|
||||
this.elementsPendingErasure.delete(elem.id);
|
||||
} else {
|
||||
this.elementsPendingErasure.add(elem.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const distance = pointDistance(
|
||||
pointFrom(pointerDownState.lastCoords.x, pointerDownState.lastCoords.y),
|
||||
pointFrom(scenePointer.x, scenePointer.y),
|
||||
const elementsToErase = this.eraserTrail.addPointToPath(
|
||||
scenePointer.x,
|
||||
scenePointer.y,
|
||||
event.altKey,
|
||||
);
|
||||
const threshold = this.getElementHitThreshold();
|
||||
const p = { ...pointerDownState.lastCoords };
|
||||
let samplingInterval = 0;
|
||||
while (samplingInterval <= distance) {
|
||||
const hitElements = this.getElementsAtPosition(p.x, p.y);
|
||||
processElements(hitElements);
|
||||
|
||||
// Exit since we reached current point
|
||||
if (samplingInterval === distance) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Calculate next point in the line at a distance of sampling interval
|
||||
samplingInterval = Math.min(samplingInterval + threshold, distance);
|
||||
|
||||
const distanceRatio = samplingInterval / distance;
|
||||
const nextX = (1 - distanceRatio) * p.x + distanceRatio * scenePointer.x;
|
||||
const nextY = (1 - distanceRatio) * p.y + distanceRatio * scenePointer.y;
|
||||
p.x = nextX;
|
||||
p.y = nextY;
|
||||
}
|
||||
|
||||
pointerDownState.lastCoords.x = scenePointer.x;
|
||||
pointerDownState.lastCoords.y = scenePointer.y;
|
||||
|
||||
if (didChange) {
|
||||
for (const element of this.scene.getNonDeletedElements()) {
|
||||
if (
|
||||
isBoundToContainer(element) &&
|
||||
(this.elementsPendingErasure.has(element.id) ||
|
||||
this.elementsPendingErasure.has(element.containerId))
|
||||
) {
|
||||
if (event.altKey) {
|
||||
this.elementsPendingErasure.delete(element.id);
|
||||
this.elementsPendingErasure.delete(element.containerId);
|
||||
} else {
|
||||
this.elementsPendingErasure.add(element.id);
|
||||
this.elementsPendingErasure.add(element.containerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.elementsPendingErasure = new Set(this.elementsPendingErasure);
|
||||
this.triggerRender();
|
||||
}
|
||||
this.elementsPendingErasure = new Set(elementsToErase);
|
||||
this.triggerRender();
|
||||
};
|
||||
|
||||
// set touch moving for mobile context menu
|
||||
|
@ -8159,7 +8056,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
}
|
||||
|
||||
if (isEraserActive(this.state)) {
|
||||
this.handleEraser(event, pointerDownState, pointerCoords);
|
||||
this.handleEraser(event, pointerCoords);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue