Stop using getTransform (#950)
* Stop using getTransform Fixes #861 The original motivation behind this is to make it work with Firefox. But it also helped make the code more intentional. Test Plan: - Create one square, select it, zoom in repeatedly, make sure that it zooms centered in the screen and everything looks good - Scroll at various zoom levels, things look good - Export a small scene at 1x and 3x, make sure the background is properly set and look good * fix selection element
This commit is contained in:
parent
dbfc8bee57
commit
b20d4539c0
@ -2246,6 +2246,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.state.selectionElement,
|
this.state.selectionElement,
|
||||||
|
window.devicePixelRatio,
|
||||||
this.rc!,
|
this.rc!,
|
||||||
this.canvas!,
|
this.canvas!,
|
||||||
{
|
{
|
||||||
|
@ -303,6 +303,10 @@ export function renderElement(
|
|||||||
context.fillStyle = "rgba(0, 0, 255, 0.10)";
|
context.fillStyle = "rgba(0, 0, 255, 0.10)";
|
||||||
context.fillRect(0, 0, element.width, element.height);
|
context.fillRect(0, 0, element.width, element.height);
|
||||||
context.fillStyle = fillStyle;
|
context.fillStyle = fillStyle;
|
||||||
|
context.translate(
|
||||||
|
-element.x - sceneState.scrollX,
|
||||||
|
-element.y - sceneState.scrollY,
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "rectangle":
|
case "rectangle":
|
||||||
|
@ -12,7 +12,6 @@ import {
|
|||||||
SCROLLBAR_COLOR,
|
SCROLLBAR_COLOR,
|
||||||
SCROLLBAR_WIDTH,
|
SCROLLBAR_WIDTH,
|
||||||
} from "../scene/scrollbars";
|
} from "../scene/scrollbars";
|
||||||
import { getZoomTranslation } from "../scene/zoom";
|
|
||||||
import { getSelectedElements } from "../scene/selection";
|
import { getSelectedElements } from "../scene/selection";
|
||||||
|
|
||||||
import { renderElement, renderElementToSvg } from "./renderElement";
|
import { renderElement, renderElementToSvg } from "./renderElement";
|
||||||
@ -28,6 +27,7 @@ export function renderScene(
|
|||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
selectionElement: ExcalidrawElement | null,
|
selectionElement: ExcalidrawElement | null,
|
||||||
|
scale: number,
|
||||||
rc: RoughCanvas,
|
rc: RoughCanvas,
|
||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
sceneState: SceneState,
|
sceneState: SceneState,
|
||||||
@ -51,46 +51,11 @@ export function renderScene(
|
|||||||
|
|
||||||
const context = canvas.getContext("2d")!;
|
const context = canvas.getContext("2d")!;
|
||||||
|
|
||||||
// Get initial scale transform as reference for later usage
|
|
||||||
const initialContextTransform = context.getTransform();
|
|
||||||
|
|
||||||
// When doing calculations based on canvas width we should used normalized one
|
// When doing calculations based on canvas width we should used normalized one
|
||||||
const normalizedCanvasWidth =
|
const normalizedCanvasWidth = canvas.width / scale;
|
||||||
canvas.width / getContextTransformScaleX(initialContextTransform);
|
const normalizedCanvasHeight = canvas.height / scale;
|
||||||
const normalizedCanvasHeight =
|
|
||||||
canvas.height / getContextTransformScaleY(initialContextTransform);
|
|
||||||
|
|
||||||
const zoomTranslation = getZoomTranslation(canvas, sceneState.zoom);
|
|
||||||
function applyZoom(context: CanvasRenderingContext2D): void {
|
|
||||||
context.save();
|
|
||||||
|
|
||||||
// Handle zoom scaling
|
|
||||||
context.setTransform(
|
|
||||||
getContextTransformScaleX(initialContextTransform) * sceneState.zoom,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
getContextTransformScaleY(initialContextTransform) * sceneState.zoom,
|
|
||||||
getContextTransformTranslateX(context.getTransform()),
|
|
||||||
getContextTransformTranslateY(context.getTransform()),
|
|
||||||
);
|
|
||||||
// Handle zoom translation
|
|
||||||
context.setTransform(
|
|
||||||
getContextTransformScaleX(context.getTransform()),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
getContextTransformScaleY(context.getTransform()),
|
|
||||||
getContextTransformTranslateX(initialContextTransform) -
|
|
||||||
zoomTranslation.x,
|
|
||||||
getContextTransformTranslateY(initialContextTransform) -
|
|
||||||
zoomTranslation.y,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
function resetZoom(context: CanvasRenderingContext2D): void {
|
|
||||||
context.restore();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paint background
|
// Paint background
|
||||||
context.save();
|
|
||||||
if (typeof sceneState.viewBackgroundColor === "string") {
|
if (typeof sceneState.viewBackgroundColor === "string") {
|
||||||
const hasTransparence =
|
const hasTransparence =
|
||||||
sceneState.viewBackgroundColor === "transparent" ||
|
sceneState.viewBackgroundColor === "transparent" ||
|
||||||
@ -99,12 +64,20 @@ export function renderScene(
|
|||||||
if (hasTransparence) {
|
if (hasTransparence) {
|
||||||
context.clearRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight);
|
context.clearRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight);
|
||||||
}
|
}
|
||||||
|
const fillStyle = context.fillStyle;
|
||||||
context.fillStyle = sceneState.viewBackgroundColor;
|
context.fillStyle = sceneState.viewBackgroundColor;
|
||||||
context.fillRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight);
|
context.fillRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight);
|
||||||
|
context.fillStyle = fillStyle;
|
||||||
} else {
|
} else {
|
||||||
context.clearRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight);
|
context.clearRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight);
|
||||||
}
|
}
|
||||||
context.restore();
|
|
||||||
|
// Apply zoom
|
||||||
|
const zoomTranslationX = (-normalizedCanvasWidth * (sceneState.zoom - 1)) / 2;
|
||||||
|
const zoomTranslationY =
|
||||||
|
(-normalizedCanvasHeight * (sceneState.zoom - 1)) / 2;
|
||||||
|
context.translate(zoomTranslationX, zoomTranslationY);
|
||||||
|
context.scale(sceneState.zoom, sceneState.zoom);
|
||||||
|
|
||||||
// Paint visible elements
|
// Paint visible elements
|
||||||
const visibleElements = elements.filter(element =>
|
const visibleElements = elements.filter(element =>
|
||||||
@ -116,15 +89,12 @@ export function renderScene(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
applyZoom(context);
|
|
||||||
visibleElements.forEach(element => {
|
visibleElements.forEach(element => {
|
||||||
renderElement(element, rc, context, renderOptimizations, sceneState);
|
renderElement(element, rc, context, renderOptimizations, sceneState);
|
||||||
});
|
});
|
||||||
resetZoom(context);
|
|
||||||
|
|
||||||
// Pain selection element
|
// Pain selection element
|
||||||
if (selectionElement) {
|
if (selectionElement) {
|
||||||
applyZoom(context);
|
|
||||||
renderElement(
|
renderElement(
|
||||||
selectionElement,
|
selectionElement,
|
||||||
rc,
|
rc,
|
||||||
@ -132,7 +102,6 @@ export function renderScene(
|
|||||||
renderOptimizations,
|
renderOptimizations,
|
||||||
sceneState,
|
sceneState,
|
||||||
);
|
);
|
||||||
resetZoom(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pain selected elements
|
// Pain selected elements
|
||||||
@ -140,7 +109,6 @@ export function renderScene(
|
|||||||
const selectedElements = getSelectedElements(elements, appState);
|
const selectedElements = getSelectedElements(elements, appState);
|
||||||
const dashledLinePadding = 4 / sceneState.zoom;
|
const dashledLinePadding = 4 / sceneState.zoom;
|
||||||
|
|
||||||
applyZoom(context);
|
|
||||||
context.translate(sceneState.scrollX, sceneState.scrollY);
|
context.translate(sceneState.scrollX, sceneState.scrollY);
|
||||||
selectedElements.forEach(element => {
|
selectedElements.forEach(element => {
|
||||||
const [
|
const [
|
||||||
@ -166,11 +134,10 @@ export function renderScene(
|
|||||||
context.lineWidth = lineWidth;
|
context.lineWidth = lineWidth;
|
||||||
context.setLineDash(initialLineDash);
|
context.setLineDash(initialLineDash);
|
||||||
});
|
});
|
||||||
resetZoom(context);
|
context.translate(-sceneState.scrollX, -sceneState.scrollY);
|
||||||
|
|
||||||
// Paint resize handlers
|
// Paint resize handlers
|
||||||
if (selectedElements.length === 1 && selectedElements[0].type !== "text") {
|
if (selectedElements.length === 1 && selectedElements[0].type !== "text") {
|
||||||
applyZoom(context);
|
|
||||||
context.translate(sceneState.scrollX, sceneState.scrollY);
|
context.translate(sceneState.scrollX, sceneState.scrollY);
|
||||||
context.fillStyle = "#fff";
|
context.fillStyle = "#fff";
|
||||||
const handlers = handlerRectangles(selectedElements[0], sceneState.zoom);
|
const handlers = handlerRectangles(selectedElements[0], sceneState.zoom);
|
||||||
@ -183,10 +150,14 @@ export function renderScene(
|
|||||||
context.strokeRect(handler[0], handler[1], handler[2], handler[3]);
|
context.strokeRect(handler[0], handler[1], handler[2], handler[3]);
|
||||||
context.lineWidth = lineWidth;
|
context.lineWidth = lineWidth;
|
||||||
});
|
});
|
||||||
resetZoom(context);
|
context.translate(-sceneState.scrollX, -sceneState.scrollY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset zoom
|
||||||
|
context.scale(1 / sceneState.zoom, 1 / sceneState.zoom);
|
||||||
|
context.translate(-zoomTranslationX, -zoomTranslationY);
|
||||||
|
|
||||||
// Paint remote pointers
|
// Paint remote pointers
|
||||||
for (const clientId in sceneState.remotePointerViewportCoords) {
|
for (const clientId in sceneState.remotePointerViewportCoords) {
|
||||||
let { x, y } = sceneState.remotePointerViewportCoords[clientId];
|
let { x, y } = sceneState.remotePointerViewportCoords[clientId];
|
||||||
@ -237,7 +208,8 @@ export function renderScene(
|
|||||||
sceneState,
|
sceneState,
|
||||||
);
|
);
|
||||||
|
|
||||||
context.save();
|
const fillStyle = context.fillStyle;
|
||||||
|
const strokeStyle = context.strokeStyle;
|
||||||
context.fillStyle = SCROLLBAR_COLOR;
|
context.fillStyle = SCROLLBAR_COLOR;
|
||||||
context.strokeStyle = "rgba(255,255,255,0.8)";
|
context.strokeStyle = "rgba(255,255,255,0.8)";
|
||||||
[scrollBars.horizontal, scrollBars.vertical].forEach(scrollBar => {
|
[scrollBars.horizontal, scrollBars.vertical].forEach(scrollBar => {
|
||||||
@ -252,7 +224,8 @@ export function renderScene(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
context.restore();
|
context.fillStyle = fillStyle;
|
||||||
|
context.strokeStyle = strokeStyle;
|
||||||
return { atLeastOneVisibleElement: visibleElements.length > 0, scrollBars };
|
return { atLeastOneVisibleElement: visibleElements.length > 0, scrollBars };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,16 +290,3 @@ export function renderSceneToSvg(
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContextTransformScaleX(transform: DOMMatrix): number {
|
|
||||||
return transform.a;
|
|
||||||
}
|
|
||||||
function getContextTransformScaleY(transform: DOMMatrix): number {
|
|
||||||
return transform.d;
|
|
||||||
}
|
|
||||||
function getContextTransformTranslateX(transform: DOMMatrix): number {
|
|
||||||
return transform.e;
|
|
||||||
}
|
|
||||||
function getContextTransformTranslateY(transform: DOMMatrix): number {
|
|
||||||
return transform.f;
|
|
||||||
}
|
|
||||||
|
@ -42,6 +42,7 @@ export function exportToCanvas(
|
|||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
null,
|
null,
|
||||||
|
scale,
|
||||||
rough.canvas(tempCanvas),
|
rough.canvas(tempCanvas),
|
||||||
tempCanvas,
|
tempCanvas,
|
||||||
{
|
{
|
||||||
|
@ -17,4 +17,4 @@ export {
|
|||||||
hasText,
|
hasText,
|
||||||
} from "./comparisons";
|
} from "./comparisons";
|
||||||
export { createScene } from "./createScene";
|
export { createScene } from "./createScene";
|
||||||
export { getZoomOrigin, getZoomTranslation, getNormalizedZoom } from "./zoom";
|
export { getZoomOrigin, getNormalizedZoom } from "./zoom";
|
||||||
|
@ -16,19 +16,6 @@ export function getZoomOrigin(canvas: HTMLCanvasElement | null) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getZoomTranslation(canvas: HTMLCanvasElement, zoom: number) {
|
|
||||||
const diffMiddleOfTheCanvas = {
|
|
||||||
x: (canvas.width / 2) * (zoom - 1),
|
|
||||||
y: (canvas.height / 2) * (zoom - 1),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Due to JavaScript float precision, we fix to fix decimals count to have symmetric zoom
|
|
||||||
return {
|
|
||||||
x: parseFloat(diffMiddleOfTheCanvas.x.toFixed(8)),
|
|
||||||
y: parseFloat(diffMiddleOfTheCanvas.y.toFixed(8)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getNormalizedZoom(zoom: number): number {
|
export function getNormalizedZoom(zoom: number): number {
|
||||||
const normalizedZoom = parseFloat(zoom.toFixed(2));
|
const normalizedZoom = parseFloat(zoom.toFixed(2));
|
||||||
const clampedZoom = Math.max(0.1, Math.min(normalizedZoom, 2));
|
const clampedZoom = Math.max(0.1, Math.min(normalizedZoom, 2));
|
||||||
|
Loading…
x
Reference in New Issue
Block a user