Disable UI rendering when history is skipped ()

When we are scrolling, resizing, or moving elements, we already disable the history. Since those actions do not change the state of the UI, we can also avoid re-drawing it and save ~10ms per frame.

I had to change all the forceUpdate() to setState({}), otherwise it would bypass shouldComponentUpdate.
This commit is contained in:
Christopher Chedeau 2020-01-26 19:08:46 +00:00 committed by GitHub
parent 263fef4eaa
commit 8ab176b9a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -180,7 +180,7 @@ export class App extends React.Component<any, AppState> {
private syncActionResult = (res: ActionResult) => { private syncActionResult = (res: ActionResult) => {
if (res.elements !== undefined) { if (res.elements !== undefined) {
elements = res.elements; elements = res.elements;
this.forceUpdate(); this.setState({});
} }
if (res.appState !== undefined) { if (res.appState !== undefined) {
@ -199,7 +199,7 @@ export class App extends React.Component<any, AppState> {
), ),
); );
elements = deleteSelectedElements(elements); elements = deleteSelectedElements(elements);
this.forceUpdate(); this.setState({});
e.preventDefault(); e.preventDefault();
}; };
private onCopy = (e: ClipboardEvent) => { private onCopy = (e: ClipboardEvent) => {
@ -226,6 +226,14 @@ export class App extends React.Component<any, AppState> {
this.saveDebounced.flush(); this.saveDebounced.flush();
}; };
public shouldComponentUpdate() {
if (!history.isRecording()) {
this.componentDidUpdate();
return false;
}
return true;
}
public async componentDidMount() { public async componentDidMount() {
document.addEventListener("copy", this.onCopy); document.addEventListener("copy", this.onCopy);
document.addEventListener("paste", this.onPaste); document.addEventListener("paste", this.onPaste);
@ -253,7 +261,7 @@ export class App extends React.Component<any, AppState> {
if (data.appState) { if (data.appState) {
this.setState(data.appState); this.setState(data.appState);
} else { } else {
this.forceUpdate(); this.setState({});
} }
} }
@ -275,7 +283,7 @@ export class App extends React.Component<any, AppState> {
public state: AppState = getDefaultAppState(); public state: AppState = getDefaultAppState();
private onResize = () => { private onResize = () => {
this.forceUpdate(); this.setState({});
}; };
private updateCurrentCursorPosition = (e: MouseEvent) => { private updateCurrentCursorPosition = (e: MouseEvent) => {
@ -286,7 +294,7 @@ export class App extends React.Component<any, AppState> {
private onKeyDown = (event: KeyboardEvent) => { private onKeyDown = (event: KeyboardEvent) => {
if (event.key === KEYS.ESCAPE && !this.state.draggingElement) { if (event.key === KEYS.ESCAPE && !this.state.draggingElement) {
elements = clearSelection(elements); elements = clearSelection(elements);
this.forceUpdate(); this.setState({});
this.setState({ elementType: "selection" }); this.setState({ elementType: "selection" });
if (window.document.activeElement instanceof HTMLElement) { if (window.document.activeElement instanceof HTMLElement) {
window.document.activeElement.blur(); window.document.activeElement.blur();
@ -320,7 +328,7 @@ export class App extends React.Component<any, AppState> {
} }
return el; return el;
}); });
this.forceUpdate(); this.setState({});
event.preventDefault(); event.preventDefault();
} else if ( } else if (
shapesShortcutKeys.includes(event.key.toLowerCase()) && shapesShortcutKeys.includes(event.key.toLowerCase()) &&
@ -505,7 +513,7 @@ export class App extends React.Component<any, AppState> {
elements = clearSelection(elements); elements = clearSelection(elements);
document.documentElement.style.cursor = document.documentElement.style.cursor =
value === "text" ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR; value === "text" ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR;
this.forceUpdate(); this.setState({});
}} }}
></ToolButton> ></ToolButton>
); );
@ -695,7 +703,7 @@ export class App extends React.Component<any, AppState> {
if (!element.isSelected) { if (!element.isSelected) {
elements = clearSelection(elements); elements = clearSelection(elements);
element.isSelected = true; element.isSelected = true;
this.forceUpdate(); this.setState({});
} }
ContextMenu.push({ ContextMenu.push({
@ -737,6 +745,8 @@ export class App extends React.Component<any, AppState> {
let deltaY = lastY - e.clientY; let deltaY = lastY - e.clientY;
lastX = e.clientX; lastX = e.clientX;
lastY = e.clientY; lastY = e.clientY;
// We don't want to save history when panning around
history.skipRecording();
this.setState(state => ({ this.setState(state => ({
scrollX: state.scrollX - deltaX, scrollX: state.scrollX - deltaX,
scrollY: state.scrollY - deltaY, scrollY: state.scrollY - deltaY,
@ -941,6 +951,8 @@ export class App extends React.Component<any, AppState> {
if (isOverHorizontalScrollBar) { if (isOverHorizontalScrollBar) {
const x = e.clientX - CANVAS_WINDOW_OFFSET_LEFT; const x = e.clientX - CANVAS_WINDOW_OFFSET_LEFT;
const dx = x - lastX; const dx = x - lastX;
// We don't want to save history when scrolling
history.skipRecording();
this.setState(state => ({ scrollX: state.scrollX - dx })); this.setState(state => ({ scrollX: state.scrollX - dx }));
lastX = x; lastX = x;
return; return;
@ -949,6 +961,8 @@ export class App extends React.Component<any, AppState> {
if (isOverVerticalScrollBar) { if (isOverVerticalScrollBar) {
const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP; const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP;
const dy = y - lastY; const dy = y - lastY;
// We don't want to save history when scrolling
history.skipRecording();
this.setState(state => ({ scrollY: state.scrollY - dy })); this.setState(state => ({ scrollY: state.scrollY - dy }));
lastY = y; lastY = y;
return; return;
@ -1051,7 +1065,7 @@ export class App extends React.Component<any, AppState> {
lastY = y; lastY = y;
// We don't want to save history when resizing an element // We don't want to save history when resizing an element
history.skipRecording(); history.skipRecording();
this.forceUpdate(); this.setState({});
return; return;
} }
} }
@ -1072,7 +1086,7 @@ export class App extends React.Component<any, AppState> {
lastY = y; lastY = y;
// We don't want to save history when dragging an element to initially size it // We don't want to save history when dragging an element to initially size it
history.skipRecording(); history.skipRecording();
this.forceUpdate(); this.setState({});
return; return;
} }
} }
@ -1127,7 +1141,7 @@ export class App extends React.Component<any, AppState> {
} }
// We don't want to save history when moving an element // We don't want to save history when moving an element
history.skipRecording(); history.skipRecording();
this.forceUpdate(); this.setState({});
}; };
const onMouseUp = (e: MouseEvent) => { const onMouseUp = (e: MouseEvent) => {
@ -1152,12 +1166,11 @@ export class App extends React.Component<any, AppState> {
this.setState({ this.setState({
draggingElement: null, draggingElement: null,
}); });
this.forceUpdate();
return; return;
} }
if (normalizeDimensions(draggingElement)) { if (normalizeDimensions(draggingElement)) {
this.forceUpdate(); this.setState({});
} }
if (resizingElement && isInvisiblySmallElement(resizingElement)) { if (resizingElement && isInvisiblySmallElement(resizingElement)) {
@ -1188,7 +1201,7 @@ export class App extends React.Component<any, AppState> {
if (draggingElement === null) { if (draggingElement === null) {
// if no element is clicked, clear the selection and redraw // if no element is clicked, clear the selection and redraw
elements = clearSelection(elements); elements = clearSelection(elements);
this.forceUpdate(); this.setState({});
return; return;
} }
@ -1208,7 +1221,7 @@ export class App extends React.Component<any, AppState> {
} }
history.resumeRecording(); history.resumeRecording();
this.forceUpdate(); this.setState({});
}; };
lastMouseUp = onMouseUp; lastMouseUp = onMouseUp;
@ -1218,7 +1231,7 @@ export class App extends React.Component<any, AppState> {
// We don't want to save history on mouseDown, only on mouseUp when it's fully configured // We don't want to save history on mouseDown, only on mouseUp when it's fully configured
history.skipRecording(); history.skipRecording();
this.forceUpdate(); this.setState({});
}} }}
onDoubleClick={e => { onDoubleClick={e => {
const { x, y } = viewportCoordsToSceneCoords(e, this.state); const { x, y } = viewportCoordsToSceneCoords(e, this.state);
@ -1253,7 +1266,7 @@ export class App extends React.Component<any, AppState> {
elements = elements.filter( elements = elements.filter(
element => element.id !== elementAtPosition.id, element => element.id !== elementAtPosition.id,
); );
this.forceUpdate(); this.setState({});
textX = textX =
this.state.scrollX + this.state.scrollX +
@ -1355,6 +1368,8 @@ export class App extends React.Component<any, AppState> {
private handleWheel = (e: WheelEvent) => { private handleWheel = (e: WheelEvent) => {
e.preventDefault(); e.preventDefault();
const { deltaX, deltaY } = e; const { deltaX, deltaY } = e;
// We don't want to save history when panning around
history.skipRecording();
this.setState(state => ({ this.setState(state => ({
scrollX: state.scrollX - deltaX, scrollX: state.scrollX - deltaX,
scrollY: state.scrollY - deltaY, scrollY: state.scrollY - deltaY,
@ -1412,7 +1427,7 @@ export class App extends React.Component<any, AppState> {
return duplicate; return duplicate;
}), }),
]; ];
this.forceUpdate(); this.setState({});
} }
}; };