feat: Animation support (#10042)

This commit is contained in:
Márk Tolmács
2025-11-10 22:31:23 +01:00
committed by GitHub
parent dc2f25c14a
commit 2b4540225d
6 changed files with 173 additions and 49 deletions
+1
View File
@@ -1813,6 +1813,7 @@ class App extends React.Component<AppProps, AppState> {
/>
)}
<InteractiveCanvas
app={this}
containerRef={this.excalidrawContainerRef}
canvas={this.interactiveCanvas}
elementsMap={elementsMap}
@@ -6,6 +6,15 @@ import {
sceneCoordsToViewportCoords,
type EditorInterface,
} 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,
@@ -13,15 +22,13 @@ import type {
} from "@excalidraw/element/types";
import { t } from "../../i18n";
import { isRenderThrottlingEnabled } from "../../reactUtils";
import { renderInteractiveScene } from "../../renderer/interactiveScene";
import type {
InteractiveCanvasRenderConfig,
RenderableElementsMap,
RenderInteractiveSceneCallback,
} from "../../scene/types";
import type { AppState, InteractiveCanvasAppState } from "../../types";
AppClassProperties,
AppState,
InteractiveCanvasAppState,
} from "../../types";
import type { DOMAttributes } from "react";
type InteractiveCanvasProps = {
@@ -37,6 +44,7 @@ type InteractiveCanvasProps = {
appState: InteractiveCanvasAppState;
renderScrollbars: boolean;
editorInterface: EditorInterface;
app: AppClassProperties;
renderInteractiveSceneCallback: (
data: RenderInteractiveSceneCallback,
) => void;
@@ -71,8 +79,11 @@ type InteractiveCanvasProps = {
>;
};
export const INTERACTIVE_SCENE_ANIMATION_KEY = "animateInteractiveScene";
const InteractiveCanvas = (props: InteractiveCanvasProps) => {
const isComponentMounted = useRef(false);
const rendererParams = useRef(null as InteractiveSceneRenderConfig | null);
useEffect(() => {
if (!isComponentMounted.current) {
@@ -129,29 +140,58 @@ const InteractiveCanvas = (props: InteractiveCanvasProps) => {
)) ||
"#6965db";
renderInteractiveScene(
{
canvas: props.canvas,
elementsMap: props.elementsMap,
visibleElements: props.visibleElements,
selectedElements: props.selectedElements,
allElementsMap: props.allElementsMap,
scale: window.devicePixelRatio,
appState: props.appState,
renderConfig: {
remotePointerViewportCoords,
remotePointerButton,
remoteSelectedElementIds,
remotePointerUsernames,
remotePointerUserStates,
selectionColor,
renderScrollbars: props.renderScrollbars,
},
editorInterface: props.editorInterface,
callback: props.renderInteractiveSceneCallback,
rendererParams.current = {
app: props.app,
canvas: props.canvas,
elementsMap: props.elementsMap,
visibleElements: props.visibleElements,
selectedElements: props.selectedElements,
allElementsMap: props.allElementsMap,
scale: window.devicePixelRatio,
appState: props.appState,
renderConfig: {
remotePointerViewportCoords,
remotePointerButton,
remoteSelectedElementIds,
remotePointerUsernames,
remotePointerUserStates,
selectionColor,
renderScrollbars: props.renderScrollbars,
},
isRenderThrottlingEnabled(),
);
editorInterface: props.editorInterface,
callback: props.renderInteractiveSceneCallback,
animationState: {
bindingHighlight: undefined,
},
deltaTime: 0,
};
if (!AnimationController.running(INTERACTIVE_SCENE_ANIMATION_KEY)) {
AnimationController.start<InteractiveSceneRenderAnimationState>(
INTERACTIVE_SCENE_ANIMATION_KEY,
({ deltaTime, state }) => {
const nextAnimationState = renderInteractiveScene({
...rendererParams.current!,
deltaTime,
animationState: state,
}).animationState;
if (nextAnimationState) {
for (const key in nextAnimationState) {
if (
nextAnimationState[
key as keyof InteractiveSceneRenderAnimationState
] !== undefined
) {
return nextAnimationState;
}
}
}
return undefined;
},
);
}
});
return (