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

View File

@@ -16,7 +16,6 @@ import {
getFeatureFlag, getFeatureFlag,
invariant, invariant,
THEME, THEME,
throttleRAF,
} from "@excalidraw/common"; } from "@excalidraw/common";
import { import {
@@ -1120,9 +1119,9 @@ const _renderInteractiveScene = ({
scale, scale,
appState, appState,
renderConfig, renderConfig,
editorInterface,
animationState, animationState,
deltaTime, deltaTime,
editorInterface,
}: InteractiveSceneRenderConfig): { }: InteractiveSceneRenderConfig): {
scrollBars?: ReturnType<typeof getScrollBars>; scrollBars?: ReturnType<typeof getScrollBars>;
atLeastOneVisibleElement: boolean; 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 * Interactive scene is the ui-canvas where we render bounding boxes, selections
* and other ui stuff. * and other ui stuff.
*/ */
export const renderInteractiveScene = < export const renderInteractiveScene = <
U extends typeof _renderInteractiveScene, U extends typeof _renderInteractiveScene,
T extends boolean = false,
>( >(
renderConfig: InteractiveSceneRenderConfig, renderConfig: InteractiveSceneRenderConfig,
throttle?: T, ): ReturnType<U> => {
): T extends true ? void : ReturnType<U> => {
if (throttle) {
renderInteractiveSceneThrottled(renderConfig);
return undefined as T extends true ? void : ReturnType<U>;
}
const ret = _renderInteractiveScene(renderConfig); const ret = _renderInteractiveScene(renderConfig);
renderConfig.callback(ret); 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 type { Scene } from "@excalidraw/element";
import { renderInteractiveSceneThrottled } from "../renderer/interactiveScene";
import { renderStaticSceneThrottled } from "../renderer/staticScene"; import { renderStaticSceneThrottled } from "../renderer/staticScene";
import type { RenderableElementsMap } from "./types"; 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 // NOTE Doesn't destroy everything (scene, rc, etc.) because it may not be
// safe to break TS contract here (for upstream cases) // safe to break TS contract here (for upstream cases)
public destroy() { public destroy() {
renderInteractiveSceneThrottled.cancel();
renderStaticSceneThrottled.cancel(); renderStaticSceneThrottled.cancel();
this.getRenderableElements.clear(); this.getRenderableElements.clear();
} }