Fix scrollbar (#735)

The computation was not correct. I'm not really sure how it used to work but it was not taking into account the dimensions of the scene so it was wrong.

The new algorithm is computing the scrollbar such that it's the position of the viewport in relationship to the bounding box of the viewport and all the elements.

Fixes #680
This commit is contained in:
Christopher Chedeau 2020-02-08 16:20:14 -08:00 committed by GitHub
parent 935a7f58a7
commit c2a3b67ccc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 73 additions and 81 deletions

View File

@ -102,9 +102,6 @@ import { copyToAppClipboard, getClipboardContent } from "./clipboard";
let { elements } = createScene(); let { elements } = createScene();
const { history } = createHistory(); const { history } = createHistory();
const CANVAS_WINDOW_OFFSET_LEFT = 0;
const CANVAS_WINDOW_OFFSET_TOP = 0;
function resetCursor() { function resetCursor() {
document.documentElement.style.cursor = ""; document.documentElement.style.cursor = "";
} }
@ -142,8 +139,8 @@ export function viewportCoordsToSceneCoords(
{ clientX, clientY }: { clientX: number; clientY: number }, { clientX, clientY }: { clientX: number; clientY: number },
{ scrollX, scrollY }: { scrollX: number; scrollY: number }, { scrollX, scrollY }: { scrollX: number; scrollY: number },
) { ) {
const x = clientX - CANVAS_WINDOW_OFFSET_LEFT - scrollX; const x = clientX - scrollX;
const y = clientY - CANVAS_WINDOW_OFFSET_TOP - scrollY; const y = clientY - scrollY;
return { x, y }; return { x, y };
} }
@ -740,8 +737,8 @@ export class App extends React.Component<any, AppState> {
}; };
public render() { public render() {
const canvasWidth = window.innerWidth - CANVAS_WINDOW_OFFSET_LEFT; const canvasWidth = window.innerWidth;
const canvasHeight = window.innerHeight - CANVAS_WINDOW_OFFSET_TOP; const canvasHeight = window.innerHeight;
return ( return (
<div className="container"> <div className="container">
@ -910,10 +907,10 @@ export class App extends React.Component<any, AppState> {
isOverVerticalScrollBar, isOverVerticalScrollBar,
} = isOverScrollBars( } = isOverScrollBars(
elements, elements,
e.clientX - CANVAS_WINDOW_OFFSET_LEFT, e.clientX / window.devicePixelRatio,
e.clientY - CANVAS_WINDOW_OFFSET_TOP, e.clientY / window.devicePixelRatio,
canvasWidth, canvasWidth / window.devicePixelRatio,
canvasHeight, canvasHeight / window.devicePixelRatio,
this.state.scrollX, this.state.scrollX,
this.state.scrollY, this.state.scrollY,
); );
@ -1096,8 +1093,8 @@ export class App extends React.Component<any, AppState> {
let lastY = y; let lastY = y;
if (isOverHorizontalScrollBar || isOverVerticalScrollBar) { if (isOverHorizontalScrollBar || isOverVerticalScrollBar) {
lastX = e.clientX - CANVAS_WINDOW_OFFSET_LEFT; lastX = e.clientX;
lastY = e.clientY - CANVAS_WINDOW_OFFSET_TOP; lastY = e.clientY;
} }
let resizeArrowFn: let resizeArrowFn:
@ -1175,7 +1172,7 @@ export class App extends React.Component<any, AppState> {
} }
if (isOverHorizontalScrollBar) { if (isOverHorizontalScrollBar) {
const x = e.clientX - CANVAS_WINDOW_OFFSET_LEFT; const x = e.clientX;
const dx = x - lastX; const dx = x - lastX;
this.setState({ scrollX: this.state.scrollX - dx }); this.setState({ scrollX: this.state.scrollX - dx });
lastX = x; lastX = x;
@ -1183,7 +1180,7 @@ export class App extends React.Component<any, AppState> {
} }
if (isOverVerticalScrollBar) { if (isOverVerticalScrollBar) {
const y = e.clientY - CANVAS_WINDOW_OFFSET_TOP; const y = e.clientY;
const dy = y - lastY; const dy = y - lastY;
this.setState({ scrollY: this.state.scrollY - dy }); this.setState({ scrollY: this.state.scrollY - dy });
lastY = y; lastY = y;
@ -1690,12 +1687,10 @@ export class App extends React.Component<any, AppState> {
textX = textX =
this.state.scrollX + this.state.scrollX +
elementAtPosition.x + elementAtPosition.x +
CANVAS_WINDOW_OFFSET_LEFT +
elementAtPosition.width / 2; elementAtPosition.width / 2;
textY = textY =
this.state.scrollY + this.state.scrollY +
elementAtPosition.y + elementAtPosition.y +
CANVAS_WINDOW_OFFSET_TOP +
elementAtPosition.height / 2; elementAtPosition.height / 2;
// x and y will change after calling newTextElement function // x and y will change after calling newTextElement function
@ -1833,13 +1828,8 @@ export class App extends React.Component<any, AppState> {
const elementsCenterX = distance(minX, maxX) / 2; const elementsCenterX = distance(minX, maxX) / 2;
const elementsCenterY = distance(minY, maxY) / 2; const elementsCenterY = distance(minY, maxY) / 2;
const dx = const dx = cursorX - this.state.scrollX - elementsCenterX;
cursorX - const dy = cursorY - this.state.scrollY - elementsCenterY;
this.state.scrollX -
CANVAS_WINDOW_OFFSET_LEFT -
elementsCenterX;
const dy =
cursorY - this.state.scrollY - CANVAS_WINDOW_OFFSET_TOP - elementsCenterY;
elements = [ elements = [
...elements, ...elements,
@ -1871,12 +1861,10 @@ export class App extends React.Component<any, AppState> {
const wysiwygX = const wysiwygX =
this.state.scrollX + this.state.scrollX +
elementClickedInside.x + elementClickedInside.x +
CANVAS_WINDOW_OFFSET_LEFT +
elementClickedInside.width / 2; elementClickedInside.width / 2;
const wysiwygY = const wysiwygY =
this.state.scrollY + this.state.scrollY +
elementClickedInside.y + elementClickedInside.y +
CANVAS_WINDOW_OFFSET_TOP +
elementClickedInside.height / 2; elementClickedInside.height / 2;
return { wysiwygX, wysiwygY, elementCenterX, elementCenterY }; return { wysiwygX, wysiwygY, elementCenterX, elementCenterY };
} }

View File

@ -1,67 +1,70 @@
import { ExcalidrawElement } from "../element/types"; import { ExcalidrawElement } from "../element/types";
import { getCommonBounds } from "../element"; import { getCommonBounds } from "../element";
const SCROLLBAR_MIN_SIZE = 15;
const SCROLLBAR_MARGIN = 4; const SCROLLBAR_MARGIN = 4;
export const SCROLLBAR_WIDTH = 6; export const SCROLLBAR_WIDTH = 6;
export const SCROLLBAR_COLOR = "rgba(0,0,0,0.3)"; export const SCROLLBAR_COLOR = "rgba(0,0,0,0.3)";
export function getScrollBars( export function getScrollBars(
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
canvasWidth: number, viewportWidth: number,
canvasHeight: number, viewportHeight: number,
scrollX: number, scrollX: number,
scrollY: number, scrollY: number,
) { ) {
let [minX, minY, maxX, maxY] = getCommonBounds(elements); // This is the bounding box of all the elements
const [
elementsMinX,
elementsMinY,
elementsMaxX,
elementsMaxY,
] = getCommonBounds(elements);
minX += scrollX; // The viewport is the rectangle currently visible for the user
maxX += scrollX; const viewportMinX = -scrollX;
minY += scrollY; const viewportMinY = -scrollY;
maxY += scrollY; const viewportMaxX = -scrollX + viewportWidth;
const viewportMaxY = -scrollY + viewportHeight;
const leftOverflow = Math.max(-minX, 0); // The scene is the bounding box of both the elements and viewport
const rightOverflow = Math.max(-(canvasWidth - maxX), 0); const sceneMinX = Math.min(elementsMinX, viewportMinX);
const topOverflow = Math.max(-minY, 0); const sceneMinY = Math.min(elementsMinY, viewportMinY);
const bottomOverflow = Math.max(-(canvasHeight - maxY), 0); const sceneMaxX = Math.max(elementsMaxX, viewportMaxX);
const sceneMaxY = Math.max(elementsMaxY, viewportMaxY);
// horizontal scrollbar // The scrollbar represents where the viewport is in relationship to the scene
let horizontalScrollBar = null;
if (leftOverflow || rightOverflow) {
horizontalScrollBar = {
x: Math.min(
leftOverflow + SCROLLBAR_MARGIN,
canvasWidth - SCROLLBAR_MIN_SIZE - SCROLLBAR_MARGIN,
),
y: canvasHeight - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN,
width: Math.max(
canvasWidth - rightOverflow - leftOverflow - SCROLLBAR_MARGIN * 2,
SCROLLBAR_MIN_SIZE,
),
height: SCROLLBAR_WIDTH,
};
}
// vertical scrollbar
let verticalScrollBar = null;
if (topOverflow || bottomOverflow) {
verticalScrollBar = {
x: canvasWidth - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN,
y: Math.min(
topOverflow + SCROLLBAR_MARGIN,
canvasHeight - SCROLLBAR_MIN_SIZE - SCROLLBAR_MARGIN,
),
width: SCROLLBAR_WIDTH,
height: Math.max(
canvasHeight - bottomOverflow - topOverflow - SCROLLBAR_WIDTH * 2,
SCROLLBAR_MIN_SIZE,
),
};
}
return { return {
horizontal: horizontalScrollBar, horizontal:
vertical: verticalScrollBar, viewportMinX === sceneMinX && viewportMaxX === sceneMaxX
? null
: {
x:
((viewportMinX - sceneMinX) / (sceneMaxX - sceneMinX)) *
viewportWidth +
SCROLLBAR_MARGIN,
y: viewportHeight - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN,
width:
((viewportMaxX - viewportMinX) / (sceneMaxX - sceneMinX)) *
viewportWidth -
SCROLLBAR_MARGIN * 2,
height: SCROLLBAR_WIDTH,
},
vertical:
viewportMinY === sceneMinY && viewportMaxY === sceneMaxY
? null
: {
x: viewportWidth - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN,
y:
((viewportMinY - sceneMinY) / (sceneMaxY - sceneMinY)) *
viewportHeight +
SCROLLBAR_MARGIN,
width: SCROLLBAR_WIDTH,
height:
((viewportMaxY - viewportMinY) / (sceneMaxY - sceneMinY)) *
viewportHeight -
SCROLLBAR_MARGIN * 2,
},
}; };
} }
@ -69,15 +72,15 @@ export function isOverScrollBars(
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
x: number, x: number,
y: number, y: number,
canvasWidth: number, viewportWidth: number,
canvasHeight: number, viewportHeight: number,
scrollX: number, scrollX: number,
scrollY: number, scrollY: number,
) { ) {
const scrollBars = getScrollBars( const scrollBars = getScrollBars(
elements, elements,
canvasWidth, viewportWidth,
canvasHeight, viewportHeight,
scrollX, scrollX,
scrollY, scrollY,
); );
@ -85,14 +88,15 @@ export function isOverScrollBars(
const [isOverHorizontalScrollBar, isOverVerticalScrollBar] = [ const [isOverHorizontalScrollBar, isOverVerticalScrollBar] = [
scrollBars.horizontal, scrollBars.horizontal,
scrollBars.vertical, scrollBars.vertical,
].map( ].map(scrollBar => {
scrollBar => return (
scrollBar && scrollBar &&
scrollBar.x <= x && scrollBar.x <= x &&
x <= scrollBar.x + scrollBar.width && x <= scrollBar.x + scrollBar.width &&
scrollBar.y <= y && scrollBar.y <= y &&
y <= scrollBar.y + scrollBar.height, y <= scrollBar.y + scrollBar.height
); );
});
return { return {
isOverHorizontalScrollBar, isOverHorizontalScrollBar,