feat: throttle pointermove
events per framerate (#4727)
This commit is contained in:
parent
96de887cc8
commit
8aff076782
@ -212,6 +212,7 @@ import {
|
||||
tupleToCoors,
|
||||
viewportCoordsToSceneCoords,
|
||||
withBatchedUpdates,
|
||||
withBatchedUpdatesThrottled,
|
||||
} from "../utils";
|
||||
import ContextMenu, { ContextMenuOption } from "./ContextMenu";
|
||||
import LayerUI from "./LayerUI";
|
||||
@ -2921,7 +2922,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
setCursor(this.canvas, CURSOR_TYPE.GRABBING);
|
||||
let { clientX: lastX, clientY: lastY } = event;
|
||||
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
|
||||
const onPointerMove = withBatchedUpdatesThrottled((event: PointerEvent) => {
|
||||
const deltaX = lastX - event.clientX;
|
||||
const deltaY = lastY - event.clientY;
|
||||
lastX = event.clientX;
|
||||
@ -2984,6 +2985,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
window.removeEventListener(EVENT.POINTER_MOVE, onPointerMove);
|
||||
window.removeEventListener(EVENT.POINTER_UP, teardown);
|
||||
window.removeEventListener(EVENT.BLUR, teardown);
|
||||
onPointerMove.flush();
|
||||
}),
|
||||
);
|
||||
window.addEventListener(EVENT.BLUR, teardown);
|
||||
@ -3086,7 +3088,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
isDraggingScrollBar = true;
|
||||
pointerDownState.lastCoords.x = event.clientX;
|
||||
pointerDownState.lastCoords.y = event.clientY;
|
||||
const onPointerMove = withBatchedUpdates((event: PointerEvent) => {
|
||||
const onPointerMove = withBatchedUpdatesThrottled((event: PointerEvent) => {
|
||||
const target = event.target;
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
@ -3105,6 +3107,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.savePointer(event.clientX, event.clientY, "up");
|
||||
window.removeEventListener(EVENT.POINTER_MOVE, onPointerMove);
|
||||
window.removeEventListener(EVENT.POINTER_UP, onPointerUp);
|
||||
onPointerMove.flush();
|
||||
});
|
||||
|
||||
lastPointerUp = onPointerUp;
|
||||
@ -3640,8 +3643,8 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
private onPointerMoveFromPointerDownHandler(
|
||||
pointerDownState: PointerDownState,
|
||||
): (event: PointerEvent) => void {
|
||||
return withBatchedUpdates((event: PointerEvent) => {
|
||||
) {
|
||||
return withBatchedUpdatesThrottled((event: PointerEvent) => {
|
||||
// We need to initialize dragOffsetXY only after we've updated
|
||||
// `state.selectedElementIds` on pointerDown. Doing it here in pointerMove
|
||||
// event handler should hopefully ensure we're already working with
|
||||
@ -4062,6 +4065,10 @@ class App extends React.Component<AppProps, AppState> {
|
||||
|
||||
lastPointerUp = null;
|
||||
|
||||
if (pointerDownState.eventListeners.onMove) {
|
||||
pointerDownState.eventListeners.onMove.flush();
|
||||
}
|
||||
|
||||
window.removeEventListener(
|
||||
EVENT.POINTER_MOVE,
|
||||
pointerDownState.eventListeners.onMove!,
|
||||
|
@ -20,7 +20,7 @@ import { LinearElementEditor } from "./element/linearElementEditor";
|
||||
import { SuggestedBinding } from "./element/binding";
|
||||
import { ImportedDataState } from "./data/types";
|
||||
import type App from "./components/App";
|
||||
import type { ResolvablePromise } from "./utils";
|
||||
import type { ResolvablePromise, throttleRAF } from "./utils";
|
||||
import { Spreadsheet } from "./charts";
|
||||
import { Language } from "./i18n";
|
||||
import { ClipboardData } from "./clipboard";
|
||||
@ -367,7 +367,7 @@ export type PointerDownState = Readonly<{
|
||||
// We need to have these in the state so that we can unsubscribe them
|
||||
eventListeners: {
|
||||
// It's defined on the initial pointer down event
|
||||
onMove: null | ((event: PointerEvent) => void);
|
||||
onMove: null | ReturnType<typeof throttleRAF>;
|
||||
// It's defined on the initial pointer down event
|
||||
onUp: null | ((event: PointerEvent) => void);
|
||||
// It's defined on the initial pointer down event
|
||||
|
62
src/utils.ts
62
src/utils.ts
@ -119,6 +119,53 @@ export const debounce = <T extends any[]>(
|
||||
return ret;
|
||||
};
|
||||
|
||||
// throttle callback to execute once per animation frame
|
||||
export const throttleRAF = <T extends any[]>(fn: (...args: T) => void) => {
|
||||
let handle: number | null = null;
|
||||
let lastArgs: T | null = null;
|
||||
let callback: ((...args: T) => void) | null = null;
|
||||
const ret = (...args: T) => {
|
||||
if (process.env.NODE_ENV === "test") {
|
||||
fn(...args);
|
||||
return;
|
||||
}
|
||||
lastArgs = args;
|
||||
callback = fn;
|
||||
if (handle === null) {
|
||||
handle = window.requestAnimationFrame(() => {
|
||||
handle = null;
|
||||
lastArgs = null;
|
||||
callback = null;
|
||||
fn(...args);
|
||||
});
|
||||
}
|
||||
};
|
||||
ret.flush = () => {
|
||||
if (handle !== null) {
|
||||
cancelAnimationFrame(handle);
|
||||
handle = null;
|
||||
}
|
||||
if (lastArgs) {
|
||||
const _lastArgs = lastArgs;
|
||||
const _callback = callback;
|
||||
lastArgs = null;
|
||||
callback = null;
|
||||
if (_callback !== null) {
|
||||
_callback(..._lastArgs);
|
||||
}
|
||||
}
|
||||
};
|
||||
ret.cancel = () => {
|
||||
lastArgs = null;
|
||||
callback = null;
|
||||
if (handle !== null) {
|
||||
cancelAnimationFrame(handle);
|
||||
handle = null;
|
||||
}
|
||||
};
|
||||
return ret;
|
||||
};
|
||||
|
||||
// https://github.com/lodash/lodash/blob/es/chunk.js
|
||||
export const chunk = <T extends any>(
|
||||
array: readonly T[],
|
||||
@ -356,6 +403,21 @@ export const withBatchedUpdates = <
|
||||
unstable_batchedUpdates(func as TFunction, event);
|
||||
}) as TFunction;
|
||||
|
||||
/**
|
||||
* barches React state updates and throttles the calls to a single call per
|
||||
* animation frame
|
||||
*/
|
||||
export const withBatchedUpdatesThrottled = <
|
||||
TFunction extends ((event: any) => void) | (() => void),
|
||||
>(
|
||||
func: Parameters<TFunction>["length"] extends 0 | 1 ? TFunction : never,
|
||||
) => {
|
||||
// @ts-ignore
|
||||
return throttleRAF<Parameters<TFunction>>(((event) => {
|
||||
unstable_batchedUpdates(func, event);
|
||||
}) as TFunction);
|
||||
};
|
||||
|
||||
//https://stackoverflow.com/a/9462382/8418
|
||||
export const nFormatter = (num: number, digits: number): string => {
|
||||
const si = [
|
||||
|
Loading…
x
Reference in New Issue
Block a user