fix: scrollbar rendering and improve dragging (#9417)

* fix: scrollbar rendering and improve dragging

* tweak offsets
This commit is contained in:
David Luzar 2025-04-20 12:28:41 +02:00 committed by GitHub
parent 5fc13e4309
commit 8fb2f70414
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 60 additions and 23 deletions

View file

@ -8787,7 +8787,10 @@ class App extends React.Component<AppProps, AppState> {
const x = event.clientX; const x = event.clientX;
const dx = x - pointerDownState.lastCoords.x; const dx = x - pointerDownState.lastCoords.x;
this.translateCanvas({ this.translateCanvas({
scrollX: this.state.scrollX - dx / this.state.zoom.value, scrollX:
this.state.scrollX -
(dx * (currentScrollBars.horizontal?.deltaMultiplier || 1)) /
this.state.zoom.value,
}); });
pointerDownState.lastCoords.x = x; pointerDownState.lastCoords.x = x;
return true; return true;
@ -8797,7 +8800,10 @@ class App extends React.Component<AppProps, AppState> {
const y = event.clientY; const y = event.clientY;
const dy = y - pointerDownState.lastCoords.y; const dy = y - pointerDownState.lastCoords.y;
this.translateCanvas({ this.translateCanvas({
scrollY: this.state.scrollY - dy / this.state.zoom.value, scrollY:
this.state.scrollY -
(dy * (currentScrollBars.vertical?.deltaMultiplier || 1)) /
this.state.zoom.value,
}); });
pointerDownState.lastCoords.y = y; pointerDownState.lastCoords.y = y;
return true; return true;

View file

@ -11,6 +11,7 @@ export 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)";
// The scrollbar represents where the viewport is in relationship to the scene
export const getScrollBars = ( export const getScrollBars = (
elements: RenderableElementsMap, elements: RenderableElementsMap,
viewportWidth: number, viewportWidth: number,
@ -31,9 +32,6 @@ export const getScrollBars = (
const viewportWidthWithZoom = viewportWidth / appState.zoom.value; const viewportWidthWithZoom = viewportWidth / appState.zoom.value;
const viewportHeightWithZoom = viewportHeight / appState.zoom.value; const viewportHeightWithZoom = viewportHeight / appState.zoom.value;
const viewportWidthDiff = viewportWidth - viewportWidthWithZoom;
const viewportHeightDiff = viewportHeight - viewportHeightWithZoom;
const safeArea = { const safeArea = {
top: parseInt(getGlobalCSSVariable("sat")) || 0, top: parseInt(getGlobalCSSVariable("sat")) || 0,
bottom: parseInt(getGlobalCSSVariable("sab")) || 0, bottom: parseInt(getGlobalCSSVariable("sab")) || 0,
@ -44,10 +42,8 @@ export const getScrollBars = (
const isRTL = getLanguage().rtl; const isRTL = getLanguage().rtl;
// The viewport is the rectangle currently visible for the user // The viewport is the rectangle currently visible for the user
const viewportMinX = const viewportMinX = -appState.scrollX + safeArea.left;
-appState.scrollX + viewportWidthDiff / 2 + safeArea.left; const viewportMinY = -appState.scrollY + safeArea.top;
const viewportMinY =
-appState.scrollY + viewportHeightDiff / 2 + safeArea.top;
const viewportMaxX = viewportMinX + viewportWidthWithZoom - safeArea.right; const viewportMaxX = viewportMinX + viewportWidthWithZoom - safeArea.right;
const viewportMaxY = viewportMinY + viewportHeightWithZoom - safeArea.bottom; const viewportMaxY = viewportMinY + viewportHeightWithZoom - safeArea.bottom;
@ -57,8 +53,43 @@ export const getScrollBars = (
const sceneMaxX = Math.max(elementsMaxX, viewportMaxX); const sceneMaxX = Math.max(elementsMaxX, viewportMaxX);
const sceneMaxY = Math.max(elementsMaxY, viewportMaxY); const sceneMaxY = Math.max(elementsMaxY, viewportMaxY);
// The scrollbar represents where the viewport is in relationship to the scene // the elements-only bbox
const sceneWidth = elementsMaxX - elementsMinX;
const sceneHeight = elementsMaxY - elementsMinY;
// scene (elements) bbox + the viewport bbox that extends outside of it
const extendedSceneWidth = sceneMaxX - sceneMinX;
const extendedSceneHeight = sceneMaxY - sceneMinY;
const scrollWidthOffset =
Math.max(SCROLLBAR_MARGIN * 2, safeArea.left + safeArea.right) +
SCROLLBAR_WIDTH * 2;
const scrollbarWidth =
viewportWidth * (viewportWidthWithZoom / extendedSceneWidth) -
scrollWidthOffset;
const scrollbarHeightOffset =
Math.max(SCROLLBAR_MARGIN * 2, safeArea.top + safeArea.bottom) +
SCROLLBAR_WIDTH * 2;
const scrollbarHeight =
viewportHeight * (viewportHeightWithZoom / extendedSceneHeight) -
scrollbarHeightOffset;
// NOTE the delta multiplier calculation isn't quite correct when viewport
// is extended outside the scene (elements) bbox as there's some small
// accumulation error. I'll let this be an exercise for others to fix. ^^
const horizontalDeltaMultiplier =
extendedSceneWidth > sceneWidth
? (extendedSceneWidth * appState.zoom.value) /
(scrollbarWidth + scrollWidthOffset)
: viewportWidth / (scrollbarWidth + scrollWidthOffset);
const verticalDeltaMultiplier =
extendedSceneHeight > sceneHeight
? (extendedSceneHeight * appState.zoom.value) /
(scrollbarHeight + scrollbarHeightOffset)
: viewportHeight / (scrollbarHeight + scrollbarHeightOffset);
return { return {
horizontal: horizontal:
viewportMinX === sceneMinX && viewportMaxX === sceneMaxX viewportMinX === sceneMinX && viewportMaxX === sceneMaxX
@ -66,18 +97,17 @@ export const getScrollBars = (
: { : {
x: x:
Math.max(safeArea.left, SCROLLBAR_MARGIN) + Math.max(safeArea.left, SCROLLBAR_MARGIN) +
((viewportMinX - sceneMinX) / (sceneMaxX - sceneMinX)) * SCROLLBAR_WIDTH +
viewportWidth, ((viewportMinX - sceneMinX) / extendedSceneWidth) * viewportWidth,
y: y:
viewportHeight - viewportHeight -
SCROLLBAR_WIDTH - SCROLLBAR_WIDTH -
Math.max(SCROLLBAR_MARGIN, safeArea.bottom), Math.max(SCROLLBAR_MARGIN, safeArea.bottom),
width: width: scrollbarWidth,
((viewportMaxX - viewportMinX) / (sceneMaxX - sceneMinX)) *
viewportWidth -
Math.max(SCROLLBAR_MARGIN * 2, safeArea.left + safeArea.right),
height: SCROLLBAR_WIDTH, height: SCROLLBAR_WIDTH,
deltaMultiplier: horizontalDeltaMultiplier,
}, },
vertical: vertical:
viewportMinY === sceneMinY && viewportMaxY === sceneMaxY viewportMinY === sceneMinY && viewportMaxY === sceneMaxY
? null ? null
@ -88,14 +118,13 @@ export const getScrollBars = (
SCROLLBAR_WIDTH - SCROLLBAR_WIDTH -
Math.max(safeArea.right, SCROLLBAR_MARGIN), Math.max(safeArea.right, SCROLLBAR_MARGIN),
y: y:
((viewportMinY - sceneMinY) / (sceneMaxY - sceneMinY)) * Math.max(safeArea.top, SCROLLBAR_MARGIN) +
viewportHeight + SCROLLBAR_WIDTH +
Math.max(safeArea.top, SCROLLBAR_MARGIN), ((viewportMinY - sceneMinY) / extendedSceneHeight) *
viewportHeight,
width: SCROLLBAR_WIDTH, width: SCROLLBAR_WIDTH,
height: height: scrollbarHeight,
((viewportMaxY - viewportMinY) / (sceneMaxY - sceneMinY)) * deltaMultiplier: verticalDeltaMultiplier,
viewportHeight -
Math.max(SCROLLBAR_MARGIN * 2, safeArea.top + safeArea.bottom),
}, },
}; };
}; };

View file

@ -130,12 +130,14 @@ export type ScrollBars = {
y: number; y: number;
width: number; width: number;
height: number; height: number;
deltaMultiplier: number;
} | null; } | null;
vertical: { vertical: {
x: number; x: number;
y: number; y: number;
width: number; width: number;
height: number; height: number;
deltaMultiplier: number;
} | null; } | null;
}; };