Add scroll spacer to fix scrollable area at minimum zoom

When at minimum zoom with "cover" behavior, the SVG canvas may be
smaller than the viewport in one dimension. This causes the browser's
scrollWidth/scrollHeight to be limited by the SVG size rather than
the full canvas extent.

Added an invisible spacer div that matches the scaled canvas dimensions,
ensuring the scrollable area always reflects the actual canvas size.
This allows proper scrolling to reach all canvas edges without going
beyond canvas boundaries.
pan-zoom
dimitrieh 2025-09-30 13:11:02 +02:00 committed by Nick O'Leary
parent f132867a31
commit cdde99b9ab
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
1 changed files with 30 additions and 3 deletions

View File

@ -336,6 +336,24 @@ RED.view = (function() {
function init() { function init() {
chart = $("#red-ui-workspace-chart"); chart = $("#red-ui-workspace-chart");
// Add invisible spacer div to ensure scrollable area matches canvas dimensions
// At minimum zoom with "cover" behavior, SVG may be smaller than viewport in one dimension
// This spacer forces the browser to calculate scrollWidth/Height based on full canvas size
// Browser's maxScroll = scrollWidth - viewport will then correctly show canvas edges
var scrollSpacer = $('<div>')
.css({
position: 'absolute',
top: 0,
left: 0,
width: space_width + 'px',
height: space_height + 'px',
pointerEvents: 'none',
visibility: 'hidden'
})
.attr('id', 'red-ui-workspace-scroll-spacer')
.appendTo(chart);
chart.on('contextmenu', function(evt) { chart.on('contextmenu', function(evt) {
if (RED.view.DEBUG) { if (RED.view.DEBUG) {
console.warn("contextmenu", { mouse_mode, event: d3.event }); console.warn("contextmenu", { mouse_mode, event: d3.event });
@ -2810,14 +2828,14 @@ RED.view = (function() {
// Calculate the minimum zoom to ensure canvas always fills the viewport (no empty space) // Calculate the minimum zoom to ensure canvas always fills the viewport (no empty space)
var viewportWidth = chart.width(); var viewportWidth = chart.width();
var viewportHeight = chart.height(); var viewportHeight = chart.height();
// Canvas is 8000x8000, calculate zoom to cover viewport // Canvas is 8000x8000, calculate zoom to cover viewport
var zoomToFitWidth = viewportWidth / space_width; var zoomToFitWidth = viewportWidth / space_width;
var zoomToFitHeight = viewportHeight / space_height; var zoomToFitHeight = viewportHeight / space_height;
// Use the LARGER zoom to ensure canvas covers entire viewport (no empty space visible) // Use the LARGER zoom to ensure canvas covers entire viewport (no empty space visible)
var calculatedMinZoom = Math.max(zoomToFitWidth, zoomToFitHeight); var calculatedMinZoom = Math.max(zoomToFitWidth, zoomToFitHeight);
// Return the larger of the calculated min or the configured min // Return the larger of the calculated min or the configured min
// This ensures canvas always fills the viewport // This ensures canvas always fills the viewport
return Math.max(calculatedMinZoom, RED.view.zoomConstants.MIN_ZOOM); return Math.max(calculatedMinZoom, RED.view.zoomConstants.MIN_ZOOM);
@ -4969,6 +4987,15 @@ RED.view = (function() {
eventLayer.attr("transform","scale("+scaleFactor+")"); eventLayer.attr("transform","scale("+scaleFactor+")");
outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor); outer.attr("width", space_width*scaleFactor).attr("height", space_height*scaleFactor);
// Update scroll spacer to match scaled canvas size
// This ensures scrollable area = canvas area
// Browser calculates maxScroll = scrollWidth - viewport, which correctly
// allows scrolling to see the far edges of canvas without going beyond
$('#red-ui-workspace-scroll-spacer').css({
width: (space_width * scaleFactor) + 'px',
height: (space_height * scaleFactor) + 'px'
});
// Don't bother redrawing nodes if we're drawing links // Don't bother redrawing nodes if we're drawing links
if (showAllLinkPorts !== -1 || mouse_mode != RED.state.JOINING) { if (showAllLinkPorts !== -1 || mouse_mode != RED.state.JOINING) {