fix: element vanishes when zoomed in (#6417)
Co-authored-by: David Luzar <luzar.david@gmail.com>
This commit is contained in:
parent
68692b9d4c
commit
705ac9c1ab
@ -87,12 +87,66 @@ export interface ExcalidrawElementWithCanvas {
|
|||||||
element: ExcalidrawElement | ExcalidrawTextElement;
|
element: ExcalidrawElement | ExcalidrawTextElement;
|
||||||
canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement;
|
||||||
theme: RenderConfig["theme"];
|
theme: RenderConfig["theme"];
|
||||||
canvasZoom: Zoom["value"];
|
scale: number;
|
||||||
canvasOffsetX: number;
|
canvasOffsetX: number;
|
||||||
canvasOffsetY: number;
|
canvasOffsetY: number;
|
||||||
boundTextElementVersion: number | null;
|
boundTextElementVersion: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cappedElementCanvasSize = (
|
||||||
|
element: NonDeletedExcalidrawElement,
|
||||||
|
zoom: Zoom,
|
||||||
|
): {
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
scale: number;
|
||||||
|
} => {
|
||||||
|
// these limits are ballpark, they depend on specific browsers and device.
|
||||||
|
// We've chosen lower limits to be safe. We might want to change these limits
|
||||||
|
// based on browser/device type, if we get reports of low quality rendering
|
||||||
|
// on zoom.
|
||||||
|
//
|
||||||
|
// ~ safari mobile canvas area limit
|
||||||
|
const AREA_LIMIT = 16777216;
|
||||||
|
// ~ safari width/height limit based on developer.mozilla.org.
|
||||||
|
const WIDTH_HEIGHT_LIMIT = 32767;
|
||||||
|
|
||||||
|
const padding = getCanvasPadding(element);
|
||||||
|
|
||||||
|
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||||
|
const elementWidth =
|
||||||
|
isLinearElement(element) || isFreeDrawElement(element)
|
||||||
|
? distance(x1, x2)
|
||||||
|
: element.width;
|
||||||
|
const elementHeight =
|
||||||
|
isLinearElement(element) || isFreeDrawElement(element)
|
||||||
|
? distance(y1, y2)
|
||||||
|
: element.height;
|
||||||
|
|
||||||
|
let width = elementWidth * window.devicePixelRatio + padding * 2;
|
||||||
|
let height = elementHeight * window.devicePixelRatio + padding * 2;
|
||||||
|
|
||||||
|
let scale: number = zoom.value;
|
||||||
|
|
||||||
|
// rescale to ensure width and height is within limits
|
||||||
|
if (
|
||||||
|
width * scale > WIDTH_HEIGHT_LIMIT ||
|
||||||
|
height * scale > WIDTH_HEIGHT_LIMIT
|
||||||
|
) {
|
||||||
|
scale = Math.min(WIDTH_HEIGHT_LIMIT / width, WIDTH_HEIGHT_LIMIT / height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// rescale to ensure canvas area is within limits
|
||||||
|
if (width * height * scale * scale > AREA_LIMIT) {
|
||||||
|
scale = Math.sqrt(AREA_LIMIT / (width * height));
|
||||||
|
}
|
||||||
|
|
||||||
|
width = Math.floor(width * scale);
|
||||||
|
height = Math.floor(height * scale);
|
||||||
|
|
||||||
|
return { width, height, scale };
|
||||||
|
};
|
||||||
|
|
||||||
const generateElementCanvas = (
|
const generateElementCanvas = (
|
||||||
element: NonDeletedExcalidrawElement,
|
element: NonDeletedExcalidrawElement,
|
||||||
zoom: Zoom,
|
zoom: Zoom,
|
||||||
@ -102,44 +156,35 @@ const generateElementCanvas = (
|
|||||||
const context = canvas.getContext("2d")!;
|
const context = canvas.getContext("2d")!;
|
||||||
const padding = getCanvasPadding(element);
|
const padding = getCanvasPadding(element);
|
||||||
|
|
||||||
|
const { width, height, scale } = 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 * scale
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
canvasOffsetY =
|
canvasOffsetY =
|
||||||
element.y > y1
|
element.y > y1
|
||||||
? distance(element.y, y1) * window.devicePixelRatio * zoom.value
|
? distance(element.y, y1) * window.devicePixelRatio * scale
|
||||||
: 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 * scale, padding * scale);
|
||||||
context.scale(
|
context.scale(
|
||||||
window.devicePixelRatio * zoom.value,
|
window.devicePixelRatio * scale,
|
||||||
window.devicePixelRatio * zoom.value,
|
window.devicePixelRatio * scale,
|
||||||
);
|
);
|
||||||
|
|
||||||
const rc = rough.canvas(canvas);
|
const rc = rough.canvas(canvas);
|
||||||
@ -156,7 +201,7 @@ const generateElementCanvas = (
|
|||||||
element,
|
element,
|
||||||
canvas,
|
canvas,
|
||||||
theme: renderConfig.theme,
|
theme: renderConfig.theme,
|
||||||
canvasZoom: zoom.value,
|
scale,
|
||||||
canvasOffsetX,
|
canvasOffsetX,
|
||||||
canvasOffsetY,
|
canvasOffsetY,
|
||||||
boundTextElementVersion: getBoundTextElement(element)?.version || null,
|
boundTextElementVersion: getBoundTextElement(element)?.version || null,
|
||||||
@ -670,7 +715,7 @@ const generateElementWithCanvas = (
|
|||||||
const prevElementWithCanvas = elementWithCanvasCache.get(element);
|
const prevElementWithCanvas = elementWithCanvasCache.get(element);
|
||||||
const shouldRegenerateBecauseZoom =
|
const shouldRegenerateBecauseZoom =
|
||||||
prevElementWithCanvas &&
|
prevElementWithCanvas &&
|
||||||
prevElementWithCanvas.canvasZoom !== zoom.value &&
|
prevElementWithCanvas.scale !== zoom.value &&
|
||||||
!renderConfig?.shouldCacheIgnoreZoom;
|
!renderConfig?.shouldCacheIgnoreZoom;
|
||||||
const boundTextElementVersion = getBoundTextElement(element)?.version || null;
|
const boundTextElementVersion = getBoundTextElement(element)?.version || null;
|
||||||
|
|
||||||
@ -701,7 +746,7 @@ const drawElementFromCanvas = (
|
|||||||
) => {
|
) => {
|
||||||
const element = elementWithCanvas.element;
|
const element = elementWithCanvas.element;
|
||||||
const padding = getCanvasPadding(element);
|
const padding = getCanvasPadding(element);
|
||||||
const zoom = elementWithCanvas.canvasZoom;
|
const zoom = elementWithCanvas.scale;
|
||||||
let [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
let [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||||
|
|
||||||
// Free draw elements will otherwise "shuffle" as the min x and y change
|
// Free draw elements will otherwise "shuffle" as the min x and y change
|
||||||
@ -728,10 +773,10 @@ const drawElementFromCanvas = (
|
|||||||
const maxDim = Math.max(distance(x1, x2), distance(y1, y2));
|
const maxDim = Math.max(distance(x1, x2), distance(y1, y2));
|
||||||
tempCanvas.width =
|
tempCanvas.width =
|
||||||
maxDim * window.devicePixelRatio * zoom +
|
maxDim * window.devicePixelRatio * zoom +
|
||||||
padding * elementWithCanvas.canvasZoom * 10;
|
padding * elementWithCanvas.scale * 10;
|
||||||
tempCanvas.height =
|
tempCanvas.height =
|
||||||
maxDim * window.devicePixelRatio * zoom +
|
maxDim * window.devicePixelRatio * zoom +
|
||||||
padding * elementWithCanvas.canvasZoom * 10;
|
padding * elementWithCanvas.scale * 10;
|
||||||
const offsetX = (tempCanvas.width - elementWithCanvas.canvas!.width) / 2;
|
const offsetX = (tempCanvas.width - elementWithCanvas.canvas!.width) / 2;
|
||||||
const offsetY = (tempCanvas.height - elementWithCanvas.canvas!.height) / 2;
|
const offsetY = (tempCanvas.height - elementWithCanvas.canvas!.height) / 2;
|
||||||
|
|
||||||
@ -812,11 +857,11 @@ const drawElementFromCanvas = (
|
|||||||
context.drawImage(
|
context.drawImage(
|
||||||
elementWithCanvas.canvas!,
|
elementWithCanvas.canvas!,
|
||||||
(x1 + renderConfig.scrollX) * window.devicePixelRatio -
|
(x1 + renderConfig.scrollX) * window.devicePixelRatio -
|
||||||
(padding * elementWithCanvas.canvasZoom) / elementWithCanvas.canvasZoom,
|
(padding * elementWithCanvas.scale) / elementWithCanvas.scale,
|
||||||
(y1 + renderConfig.scrollY) * window.devicePixelRatio -
|
(y1 + renderConfig.scrollY) * window.devicePixelRatio -
|
||||||
(padding * elementWithCanvas.canvasZoom) / elementWithCanvas.canvasZoom,
|
(padding * elementWithCanvas.scale) / elementWithCanvas.scale,
|
||||||
elementWithCanvas.canvas!.width / elementWithCanvas.canvasZoom,
|
elementWithCanvas.canvas!.width / elementWithCanvas.scale,
|
||||||
elementWithCanvas.canvas!.height / elementWithCanvas.canvasZoom,
|
elementWithCanvas.canvas!.height / elementWithCanvas.scale,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user