Compare commits
32 Commits
master
...
zsviczian-
Author | SHA1 | Date | |
---|---|---|---|
![]() |
b82a0749b1 | ||
![]() |
a772362599 | ||
![]() |
3f11ca0a44 | ||
![]() |
808e4711f9 | ||
![]() |
a26e8bade8 | ||
![]() |
fc7135c5d1 | ||
![]() |
f017a60101 | ||
![]() |
17f9f64eda | ||
![]() |
619e4061f5 | ||
![]() |
028ad1ee81 | ||
![]() |
09a05a4a1c | ||
![]() |
c4738b31fb | ||
![]() |
87e6638e9e | ||
![]() |
0a6d41ecf9 | ||
![]() |
11109fcc62 | ||
![]() |
c7346e3a77 | ||
![]() |
fd030de669 | ||
![]() |
f77975cee5 | ||
![]() |
f994e5d71d | ||
![]() |
77028f4d08 | ||
![]() |
fb29bb4816 | ||
![]() |
23fcddb2a3 | ||
![]() |
b314b939b2 | ||
![]() |
bc687fea1b | ||
![]() |
e15f313fe7 | ||
![]() |
9f02922c91 | ||
![]() |
2b6819eb2d | ||
![]() |
c0e9b8d7bc | ||
![]() |
c8c683c025 | ||
![]() |
2117fbbc57 | ||
![]() |
3b9953f57f | ||
![]() |
7d1efb7f8b |
@ -1361,6 +1361,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
document.querySelector(".excalidraw")!,
|
document.querySelector(".excalidraw")!,
|
||||||
).getPropertyValue("--color-selection");
|
).getPropertyValue("--color-selection");
|
||||||
|
|
||||||
|
//const now = Date.now();
|
||||||
|
//if (!this.state.shouldCacheIgnoreZoom) {
|
||||||
|
// console.log(`renderScene`, now);
|
||||||
|
//}
|
||||||
renderScene(
|
renderScene(
|
||||||
{
|
{
|
||||||
elements: renderingElements,
|
elements: renderingElements,
|
||||||
@ -1397,11 +1401,14 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
if (this.state.scrolledOutside !== scrolledOutside) {
|
if (this.state.scrolledOutside !== scrolledOutside) {
|
||||||
this.setState({ scrolledOutside });
|
this.setState({ scrolledOutside });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.scheduleImageRefresh();
|
this.scheduleImageRefresh();
|
||||||
|
//if (!this.state.shouldCacheIgnoreZoom) {
|
||||||
|
// setTimeout(() => console.log(`after renderScene`, now));
|
||||||
|
//}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
THROTTLE_NEXT_RENDER && window.EXCALIDRAW_THROTTLE_RENDER === true,
|
true ||
|
||||||
|
(THROTTLE_NEXT_RENDER && window.EXCALIDRAW_THROTTLE_RENDER === true),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!THROTTLE_NEXT_RENDER) {
|
if (!THROTTLE_NEXT_RENDER) {
|
||||||
|
@ -30,7 +30,7 @@ import { RenderConfig } from "../scene/types";
|
|||||||
import { distance, getFontString, getFontFamilyString, isRTL } from "../utils";
|
import { distance, getFontString, getFontFamilyString, isRTL } from "../utils";
|
||||||
import { getCornerRadius, isPathALoop, isRightAngle } from "../math";
|
import { getCornerRadius, isPathALoop, isRightAngle } from "../math";
|
||||||
import rough from "roughjs/bin/rough";
|
import rough from "roughjs/bin/rough";
|
||||||
import { AppState, BinaryFiles, Zoom } from "../types";
|
import { AppState, BinaryFiles, NormalizedZoomValue, Zoom } from "../types";
|
||||||
import { getDefaultAppState } from "../appState";
|
import { getDefaultAppState } from "../appState";
|
||||||
import {
|
import {
|
||||||
BOUND_TEXT_PADDING,
|
BOUND_TEXT_PADDING,
|
||||||
@ -93,6 +93,50 @@ export interface ExcalidrawElementWithCanvas {
|
|||||||
boundTextElementVersion: number | null;
|
boundTextElementVersion: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const cappedElementCanvasSize = (
|
||||||
|
element: NonDeletedExcalidrawElement,
|
||||||
|
zoom: Zoom,
|
||||||
|
): {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
zoomValue: NormalizedZoomValue;
|
||||||
|
} => {
|
||||||
|
const sizelimit = 16777216; // 2^24
|
||||||
|
const padding = getCanvasPadding(element);
|
||||||
|
let zoomValue = zoom.value;
|
||||||
|
|
||||||
|
if (isLinearElement(element) || isFreeDrawElement(element)) {
|
||||||
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||||
|
|
||||||
|
let width = distance(x1, x2) * window.devicePixelRatio + padding * 2;
|
||||||
|
let height = distance(y1, y2) * window.devicePixelRatio + padding * 2;
|
||||||
|
|
||||||
|
const size = width * height * zoomValue * zoomValue;
|
||||||
|
if (size > sizelimit) {
|
||||||
|
zoomValue = Math.sqrt(
|
||||||
|
sizelimit / (width * height),
|
||||||
|
) as NormalizedZoomValue;
|
||||||
|
width = distance(x1, x2) * window.devicePixelRatio + padding * 2;
|
||||||
|
height = distance(y1, y2) * window.devicePixelRatio + padding * 2;
|
||||||
|
}
|
||||||
|
width *= zoomValue;
|
||||||
|
height *= zoomValue;
|
||||||
|
return { width, height, zoomValue };
|
||||||
|
}
|
||||||
|
let width = element.width * window.devicePixelRatio + padding * 2;
|
||||||
|
let height = element.height * window.devicePixelRatio + padding * 2;
|
||||||
|
|
||||||
|
const size = width * height * zoomValue * zoomValue;
|
||||||
|
if (size > sizelimit) {
|
||||||
|
zoomValue = Math.sqrt(sizelimit / (width * height)) as NormalizedZoomValue;
|
||||||
|
width = element.width * window.devicePixelRatio + padding * 2;
|
||||||
|
height = element.height * window.devicePixelRatio + padding * 2;
|
||||||
|
}
|
||||||
|
width *= zoomValue;
|
||||||
|
height *= zoomValue;
|
||||||
|
return { width, height, zoomValue };
|
||||||
|
};
|
||||||
|
|
||||||
const generateElementCanvas = (
|
const generateElementCanvas = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
zoom: Zoom,
|
zoom: Zoom,
|
||||||
@ -102,44 +146,35 @@ const generateElementCanvas = (
|
|||||||
const context = canvas.getContext("2d")!;
|
const context = canvas.getContext("2d")!;
|
||||||
const padding = getCanvasPadding(element);
|
const padding = getCanvasPadding(element);
|
||||||
|
|
||||||
|
const { width, height, zoomValue } = cappedElementCanvasSize(element, zoom);
|
||||||
|
|
||||||
|
canvas.width = width;
|
||||||
|
canvas.height = height;
|
||||||
|
|
||||||
let canvasOffsetX = 0;
|
let canvasOffsetX = 0;
|
||||||
let canvasOffsetY = 0;
|
let canvasOffsetY = 0;
|
||||||
|
|
||||||
if (isLinearElement(element) || isFreeDrawElement(element)) {
|
if (isLinearElement(element) || isFreeDrawElement(element)) {
|
||||||
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
const [x1, y1] = getElementAbsoluteCoords(element);
|
||||||
|
|
||||||
canvas.width =
|
|
||||||
distance(x1, x2) * window.devicePixelRatio * zoom.value +
|
|
||||||
padding * zoom.value * 2;
|
|
||||||
canvas.height =
|
|
||||||
distance(y1, y2) * window.devicePixelRatio * zoom.value +
|
|
||||||
padding * zoom.value * 2;
|
|
||||||
|
|
||||||
canvasOffsetX =
|
canvasOffsetX =
|
||||||
element.x > x1
|
element.x > x1
|
||||||
? distance(element.x, x1) * window.devicePixelRatio * zoom.value
|
? distance(element.x, x1) * window.devicePixelRatio * zoomValue
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
canvasOffsetY =
|
canvasOffsetY =
|
||||||
element.y > y1
|
element.y > y1
|
||||||
? distance(element.y, y1) * window.devicePixelRatio * zoom.value
|
? distance(element.y, y1) * window.devicePixelRatio * zoomValue
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
context.translate(canvasOffsetX, canvasOffsetY);
|
context.translate(canvasOffsetX, canvasOffsetY);
|
||||||
} else {
|
|
||||||
canvas.width =
|
|
||||||
element.width * window.devicePixelRatio * zoom.value +
|
|
||||||
padding * zoom.value * 2;
|
|
||||||
canvas.height =
|
|
||||||
element.height * window.devicePixelRatio * zoom.value +
|
|
||||||
padding * zoom.value * 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
context.save();
|
context.save();
|
||||||
context.translate(padding * zoom.value, padding * zoom.value);
|
context.translate(padding * zoomValue, padding * zoomValue);
|
||||||
context.scale(
|
context.scale(
|
||||||
window.devicePixelRatio * zoom.value,
|
window.devicePixelRatio * zoomValue,
|
||||||
window.devicePixelRatio * zoom.value,
|
window.devicePixelRatio * zoomValue,
|
||||||
);
|
);
|
||||||
|
|
||||||
const rc = rough.canvas(canvas);
|
const rc = rough.canvas(canvas);
|
||||||
@ -156,7 +191,7 @@ const generateElementCanvas = (
|
|||||||
element,
|
element,
|
||||||
canvas,
|
canvas,
|
||||||
theme: renderConfig.theme,
|
theme: renderConfig.theme,
|
||||||
canvasZoom: zoom.value,
|
canvasZoom: zoomValue,
|
||||||
canvasOffsetX,
|
canvasOffsetX,
|
||||||
canvasOffsetY,
|
canvasOffsetY,
|
||||||
boundTextElementVersion: getBoundTextElement(element)?.version || null,
|
boundTextElementVersion: getBoundTextElement(element)?.version || null,
|
||||||
@ -422,6 +457,11 @@ const generateElementShape = (
|
|||||||
// `null` indicates no rc shape applicable for this element type
|
// `null` indicates no rc shape applicable for this element type
|
||||||
// (= do not generate anything)
|
// (= do not generate anything)
|
||||||
if (shape === undefined) {
|
if (shape === undefined) {
|
||||||
|
const prevElementWithCanvas = elementWithCanvasCache.get(element);
|
||||||
|
if (prevElementWithCanvas?.canvas) {
|
||||||
|
prevElementWithCanvas.canvas.width = 0;
|
||||||
|
prevElementWithCanvas.canvas.height = 0;
|
||||||
|
}
|
||||||
elementWithCanvasCache.delete(element);
|
elementWithCanvasCache.delete(element);
|
||||||
|
|
||||||
switch (element.type) {
|
switch (element.type) {
|
||||||
@ -685,7 +725,10 @@ const generateElementWithCanvas = (
|
|||||||
zoom,
|
zoom,
|
||||||
renderConfig,
|
renderConfig,
|
||||||
);
|
);
|
||||||
|
if (prevElementWithCanvas?.canvas) {
|
||||||
|
prevElementWithCanvas.canvas.width = 0;
|
||||||
|
prevElementWithCanvas.canvas.height = 0;
|
||||||
|
}
|
||||||
elementWithCanvasCache.set(element, elementWithCanvas);
|
elementWithCanvasCache.set(element, elementWithCanvas);
|
||||||
|
|
||||||
return elementWithCanvas;
|
return elementWithCanvas;
|
||||||
|
@ -29,7 +29,11 @@ import {
|
|||||||
} from "../scene/scrollbars";
|
} from "../scene/scrollbars";
|
||||||
import { getSelectedElements } from "../scene/selection";
|
import { getSelectedElements } from "../scene/selection";
|
||||||
|
|
||||||
import { renderElement, renderElementToSvg } from "./renderElement";
|
import {
|
||||||
|
cappedElementCanvasSize,
|
||||||
|
renderElement,
|
||||||
|
renderElementToSvg,
|
||||||
|
} from "./renderElement";
|
||||||
import { getClientColors } from "../clients";
|
import { getClientColors } from "../clients";
|
||||||
import { LinearElementEditor } from "../element/linearElementEditor";
|
import { LinearElementEditor } from "../element/linearElementEditor";
|
||||||
import {
|
import {
|
||||||
@ -407,6 +411,21 @@ export const _renderScene = ({
|
|||||||
|
|
||||||
let editingLinearElement: NonDeleted<ExcalidrawLinearElement> | undefined =
|
let editingLinearElement: NonDeleted<ExcalidrawLinearElement> | undefined =
|
||||||
undefined;
|
undefined;
|
||||||
|
const start = Date.now();
|
||||||
|
const showDebug = false; //!appState.shouldCacheIgnoreZoom && (appState.zoom.value < 0.5);
|
||||||
|
if (showDebug) {
|
||||||
|
console.log("start: renderElements");
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
visibleElements.length,
|
||||||
|
appState.zoom.value,
|
||||||
|
Math.round(
|
||||||
|
visibleElements.reduce((acc, el) => {
|
||||||
|
const { width, height } = cappedElementCanvasSize(el, appState.zoom);
|
||||||
|
return acc + width * height;
|
||||||
|
}, 0),
|
||||||
|
),
|
||||||
|
);
|
||||||
visibleElements.forEach((element) => {
|
visibleElements.forEach((element) => {
|
||||||
try {
|
try {
|
||||||
renderElement(element, rc, context, renderConfig, appState);
|
renderElement(element, rc, context, renderConfig, appState);
|
||||||
@ -426,7 +445,9 @@ export const _renderScene = ({
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
if (showDebug) {
|
||||||
|
console.log(`finish: renderElements ${Date.now() - start}`);
|
||||||
|
}
|
||||||
if (editingLinearElement) {
|
if (editingLinearElement) {
|
||||||
renderLinearPointHandles(
|
renderLinearPointHandles(
|
||||||
context,
|
context,
|
||||||
@ -636,10 +657,8 @@ export const _renderScene = ({
|
|||||||
}
|
}
|
||||||
context.restore();
|
context.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset zoom
|
// Reset zoom
|
||||||
context.restore();
|
context.restore();
|
||||||
|
|
||||||
// Paint remote pointers
|
// Paint remote pointers
|
||||||
for (const clientId in renderConfig.remotePointerViewportCoords) {
|
for (const clientId in renderConfig.remotePointerViewportCoords) {
|
||||||
let { x, y } = renderConfig.remotePointerViewportCoords[clientId];
|
let { x, y } = renderConfig.remotePointerViewportCoords[clientId];
|
||||||
@ -787,7 +806,6 @@ export const _renderScene = ({
|
|||||||
});
|
});
|
||||||
context.restore();
|
context.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
context.restore();
|
context.restore();
|
||||||
return { atLeastOneVisibleElement: visibleElements.length > 0, scrollBars };
|
return { atLeastOneVisibleElement: visibleElements.length > 0, scrollBars };
|
||||||
};
|
};
|
||||||
|
21
src/utils.ts
21
src/utils.ts
@ -135,17 +135,24 @@ export const throttleRAF = <T extends any[]>(
|
|||||||
let timerId: number | null = null;
|
let timerId: number | null = null;
|
||||||
let lastArgs: T | null = null;
|
let lastArgs: T | null = null;
|
||||||
let lastArgsTrailing: T | null = null;
|
let lastArgsTrailing: T | null = null;
|
||||||
|
let watchdog: number | null = null;
|
||||||
|
|
||||||
const scheduleFunc = (args: T) => {
|
const scheduleFunc = (args: T) => {
|
||||||
timerId = window.requestAnimationFrame(() => {
|
timerId = window.requestAnimationFrame(() => {
|
||||||
timerId = null;
|
timerId = null;
|
||||||
|
//console.log("start render in animation frame");
|
||||||
fn(...args);
|
fn(...args);
|
||||||
|
//console.log("render done in animation frame");
|
||||||
lastArgs = null;
|
lastArgs = null;
|
||||||
if (lastArgsTrailing) {
|
if (lastArgsTrailing) {
|
||||||
|
//console.log("last args trailing", lastArgsTrailing);
|
||||||
lastArgs = lastArgsTrailing;
|
lastArgs = lastArgsTrailing;
|
||||||
lastArgsTrailing = null;
|
lastArgsTrailing = null;
|
||||||
scheduleFunc(lastArgs);
|
scheduleFunc(lastArgs);
|
||||||
}
|
}
|
||||||
|
if (watchdog) {
|
||||||
|
clearTimeout(watchdog);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -165,6 +172,9 @@ export const throttleRAF = <T extends any[]>(
|
|||||||
if (timerId !== null) {
|
if (timerId !== null) {
|
||||||
cancelAnimationFrame(timerId);
|
cancelAnimationFrame(timerId);
|
||||||
timerId = null;
|
timerId = null;
|
||||||
|
if (watchdog) {
|
||||||
|
clearTimeout(watchdog);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (lastArgs) {
|
if (lastArgs) {
|
||||||
fn(...(lastArgsTrailing || lastArgs));
|
fn(...(lastArgsTrailing || lastArgs));
|
||||||
@ -176,8 +186,19 @@ export const throttleRAF = <T extends any[]>(
|
|||||||
if (timerId !== null) {
|
if (timerId !== null) {
|
||||||
cancelAnimationFrame(timerId);
|
cancelAnimationFrame(timerId);
|
||||||
timerId = null;
|
timerId = null;
|
||||||
|
if (watchdog) {
|
||||||
|
clearTimeout(watchdog);
|
||||||
|
watchdog = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
watchdog = window.setTimeout(() => {
|
||||||
|
console.log("watchdog", timerId);
|
||||||
|
if (timerId !== null) {
|
||||||
|
cancelAnimationFrame(timerId);
|
||||||
|
timerId = null;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user