feat: Animation support (#10042)

This commit is contained in:
Márk Tolmács
2025-11-10 22:31:23 +01:00
committed by Mark Tolmacs
parent f6fb3d294f
commit 69a299aa82
3 changed files with 17 additions and 37 deletions

View File

@@ -8,6 +8,14 @@ import {
} from "@excalidraw/common";
import { AnimationController } from "@excalidraw/excalidraw/renderer/animation";
import type {
InteractiveCanvasRenderConfig,
InteractiveSceneRenderAnimationState,
InteractiveSceneRenderConfig,
RenderableElementsMap,
RenderInteractiveSceneCallback,
} from "@excalidraw/excalidraw/scene/types";
import type {
NonDeletedExcalidrawElement,
NonDeletedSceneElementsMap,
@@ -16,13 +24,6 @@ import type {
import { t } from "../../i18n";
import { renderInteractiveScene } from "../../renderer/interactiveScene";
import type {
InteractiveCanvasRenderConfig,
InteractiveSceneRenderAnimationState,
InteractiveSceneRenderConfig,
RenderableElementsMap,
RenderInteractiveSceneCallback,
} from "../../scene/types";
import type {
AppClassProperties,
AppState,
@@ -148,7 +149,6 @@ const InteractiveCanvas = (props: InteractiveCanvasProps) => {
allElementsMap: props.allElementsMap,
scale: window.devicePixelRatio,
appState: props.appState,
editorInterface: props.editorInterface,
renderConfig: {
remotePointerViewportCoords,
remotePointerButton,
@@ -160,6 +160,7 @@ const InteractiveCanvas = (props: InteractiveCanvasProps) => {
// NOTE not memoized on so we don't rerender on cursor move
lastViewportPosition: props.app.lastViewportPosition,
},
editorInterface: props.editorInterface,
callback: props.renderInteractiveSceneCallback,
animationState: {
bindingHighlight: undefined,
@@ -171,14 +172,11 @@ const InteractiveCanvas = (props: InteractiveCanvasProps) => {
AnimationController.start<InteractiveSceneRenderAnimationState>(
INTERACTIVE_SCENE_ANIMATION_KEY,
({ deltaTime, state }) => {
const nextAnimationState = renderInteractiveScene(
{
const nextAnimationState = renderInteractiveScene({
...rendererParams.current!,
deltaTime,
animationState: state,
},
false,
).animationState;
}).animationState;
if (nextAnimationState) {
for (const key in nextAnimationState) {

View File

@@ -16,7 +16,6 @@ import {
getFeatureFlag,
invariant,
THEME,
throttleRAF,
} from "@excalidraw/common";
import {
@@ -1120,9 +1119,9 @@ const _renderInteractiveScene = ({
scale,
appState,
renderConfig,
editorInterface,
animationState,
deltaTime,
editorInterface,
}: InteractiveSceneRenderConfig): {
scrollBars?: ReturnType<typeof getScrollBars>;
atLeastOneVisibleElement: boolean;
@@ -1607,31 +1606,16 @@ const _renderInteractiveScene = ({
};
};
/** throttled to animation framerate */
export const renderInteractiveSceneThrottled = throttleRAF(
(config: InteractiveSceneRenderConfig) => {
const ret = _renderInteractiveScene(config);
config.callback?.(ret);
},
{ trailing: true },
);
/**
* Interactive scene is the ui-canvas where we render bounding boxes, selections
* and other ui stuff.
*/
export const renderInteractiveScene = <
U extends typeof _renderInteractiveScene,
T extends boolean = false,
>(
renderConfig: InteractiveSceneRenderConfig,
throttle?: T,
): T extends true ? void : ReturnType<U> => {
if (throttle) {
renderInteractiveSceneThrottled(renderConfig);
return undefined as T extends true ? void : ReturnType<U>;
}
): ReturnType<U> => {
const ret = _renderInteractiveScene(renderConfig);
renderConfig.callback(ret);
return ret as T extends true ? void : ReturnType<U>;
return ret as ReturnType<U>;
};

View File

@@ -10,7 +10,6 @@ import type {
import type { Scene } from "@excalidraw/element";
import { renderInteractiveSceneThrottled } from "../renderer/interactiveScene";
import { renderStaticSceneThrottled } from "../renderer/staticScene";
import type { RenderableElementsMap } from "./types";
@@ -150,7 +149,6 @@ export class Renderer {
// NOTE Doesn't destroy everything (scene, rc, etc.) because it may not be
// safe to break TS contract here (for upstream cases)
public destroy() {
renderInteractiveSceneThrottled.cancel();
renderStaticSceneThrottled.cancel();
this.getRenderableElements.clear();
}