node-red/CANVAS_INTERACTION.md

18 KiB

Canvas Interaction Improvements

This document tracks the ongoing improvements to Node-RED's canvas interaction across different devices, input methods, and browser zoom settings.

Objectives

Improve canvas interaction to work consistently and intuitively across:

  • Browser zoom levels: 100%, 125%, 150%, 200%, etc.
  • Input devices: Mouse, trackpad, and touchscreen
  • Platforms: Desktop (Windows, macOS, Linux) and mobile/tablet devices

What Has Been Implemented

Zoom Button & Hotkey Enhancements (Session 2)

Zoom-to-Fit Feature (commits: 6f164a8, 788d0a3, ed71cf9, 732c828)

  • New zoom-to-fit button in footer toolbar
  • Shows all nodes with padding, properly centered in viewport
  • Keyboard shortcut: Ctrl+1 / Cmd+1 for zoom-to-fit
  • Respects maximum zoom level (won't zoom in beyond 1.0)
  • Works immediately on page load (refreshes active nodes first)
  • Smooth animated pan when zoom doesn't need to change
  • Pan animation duration matches zoom animation (200-350ms)

Smooth Zoom Button Animation (commits: 935ff62, f07907e)

  • Smooth 200-350ms zoom transitions for button/hotkey zoom
  • Performance optimized: only updates transforms during animation
  • No viewport jumping or drift
  • Focal point locking for sequential zooms (1 second timeout)
  • Maintains viewport center across multiple rapid button presses

Dynamic Zoom Animation Duration (commit: 594c0d6)

  • Animation duration scales with zoom distance (logarithmic)
  • Consistent perceived velocity across all zoom levels
  • Range: 200-350ms (faster for small changes, slower for large)
  • Pan-only animations also scale with distance traveled
  • Reference: doubling/halving zoom takes ~250ms

Visual Feedback Enhancements (commits: 6261213, ce5a031, 0247a91)

  • Grab cursor (open hand) when spacebar pressed
  • Grabbing cursor (closed hand) during active pan
  • Works for spacebar+click and middle-click pan modes
  • Minimap auto-shows during zoom button/hotkey operations
  • Minimap auto-shows during zoom-to-fit and zoom reset

Zoom Limits (commit: 45c2a79)

  • Maximum zoom level set to 1.0 (100%, no zooming in beyond 1:1)
  • All zoom methods respect this limit
  • Zoom-to-fit properly clamps to max zoom

Bug Fixes (commits: be1da36, 7fdff0c, 6e49e96, e3de29d)

  • Fixed grey padding at canvas bottom (SVG margin reset)
  • Fixed zoom button direction (were reversed)
  • Fixed viewport drift without focal point
  • Fixed zoom center calculation consistency

Zoom Functionality

Smooth Zoom Animation (commits: bdfa06b, a12b65b)

  • 125ms smooth zoom transitions with ease-out curves
  • Natural acceleration/deceleration for zoom operations
  • Reduced acceleration from 2x to 1.2x max for better control
  • Asymmetric zoom speeds (zoom out 40-50% slower than zoom in)
  • Gentler acceleration range (0.7-1.1) for smoother transitions
  • No jarring animations during mouse wheel zoom

Zoom Input Methods (commits: e7a028b, bdfa06b)

  • Mouse wheel zoom
  • Alt+scroll zoom mode (keyboard modifier alternative)
  • Space+scroll zoom mode (keyboard modifier alternative)
  • Trackpad pinch-to-zoom (browsers translate to Ctrl+wheel events)
  • Touch screen pinch-to-zoom with proper center tracking (direct touch events)
  • UI zoom buttons (corrected zoom in/out direction)
  • Zoom-to-fit button (zooms out to show all nodes with padding, respects minimum zoom)

Note: Ctrl+wheel is used for trackpad pinch gestures on desktop. Browsers automatically translate two-finger pinch gestures on trackpads into Ctrl+wheel events. This is separate from touchscreen pinch-to-zoom, which uses direct touch events (touchstart/touchmove/touchend).

Zoom Focal Point (commits: e42b09de, feec7ec, e7a028b)

  • Cursor-centered zoom (focuses on cursor position)
  • Store focal point in workspace coordinates instead of screen coordinates
  • Prevents focal point drift when scroll changes due to canvas boundaries
  • Maintains consistent zoom focus even when view shifts at edges
  • Fixed focal point during pinch gestures

Zoom Direction & Behavior (commits: 37f9786, bdfa06b)

  • Fixed trackpad zoom direction (spreading fingers zooms in, pinching zooms out)
  • Matches standard macOS trackpad behavior
  • Proper ratio-based scaling for pinch gestures
  • Scale lock issues fixed with improved tolerance handling

Dynamic Zoom Limits (commits: 7918693, f13ed66)

  • Calculate minimum zoom dynamically based on viewport size
  • Ensure canvas always covers entire viewport (no empty space visible)
  • Use 'cover' behavior: canvas fills viewport completely
  • Recalculate minimum zoom on window resize
  • Automatically adjust zoom if current level falls below new minimum after resize
  • Prevent zooming out beyond what's needed to fill viewport

Panning Functionality

Pan Input Methods (commit: feec7ec)

  • Two-finger pan gesture for touch devices
  • Spacebar+left-click panning for desktop
  • Mode locking to prevent laggy gesture switching
  • Lock into pan or zoom mode based on initial movement
  • Better gesture detection thresholds (10px for zoom, 5px for pan)

Scroll Behavior (commit: e7a028b)

  • Momentum scrolling with edge bounce animation
  • Enhanced spacebar handling to prevent scroll artifacts

UI/UX Enhancements

Gesture State Management (commits: e42b09de, bdfa06b, 121982e)

  • Improved gesture state management for trackpad and touch gestures
  • Proper state cleanup when cursor leaves canvas
  • Clear touchStartTime timeout when entering two-finger pan mode
  • Prevent interference between long-press detection and pan gestures

UI Pinch-Zoom Prevention (commit: e0c5b84)

  • Prevent UI pinch-to-zoom while keeping canvas zoomable
  • Apply touch-action: pan-x pan-y to html, body, and editor elements
  • Apply touch-action: none to canvas for custom gestures
  • JavaScript prevention for trackpad pinch on non-canvas areas
  • Block Ctrl+wheel events outside the workspace chart

Minimap Navigation (commits: 53dce6a, 5e056a4)

  • Auto-show minimap on zoom and pan operations
  • Auto-show minimap on flow startup and workspace changes
  • Minimap appears for 2 seconds during navigation then fades out
  • Smooth fade in/out animations for minimap visibility
  • Minimap stays visible if manually toggled with button
  • Emit view:navigate events for all zoom and pan operations
  • Check for active nodes before showing on workspace change

Visual Polish (commit: 53dce6a)

  • Hide scrollbars on canvas while keeping it scrollable
  • Clean visual appearance without visible scrollbars

Code Architecture

New Modules (commit: bdfa06b)

  • view-zoom-animator.js - Zoom animation utilities (223 lines)
  • view-zoom-constants.js - Zoom configuration constants (21 lines)
  • Updated Gruntfile to include new zoom modules in build

Current Expectations

Cross-Device Consistency

  • Zoom and pan should feel natural on mouse, trackpad, and touchscreen
  • Gestures should be responsive without lag or mode switching artifacts
  • Zoom focal point should remain stable regardless of input method

Browser Zoom Compatibility

  • Canvas interaction should work correctly at all browser zoom levels
  • UI elements should remain accessible and functional
  • No layout breaking or interaction dead zones

Visual Feedback

  • Minimap should provide contextual navigation feedback
  • Smooth animations should make interactions feel polished
  • No visual glitches or artifacts during zoom/pan operations

Performance

  • All interactions should be smooth (60fps target)
  • No janky animations or delayed responses
  • Efficient gesture detection without excessive computation

Recent Fixes

Grey Padding at Canvas Bottom (Latest)

Issue: When scrolled to the bottom of the canvas, 5 pixels of grey space appeared below the grid, allowing users to scroll slightly beyond the canvas boundary.

Root Cause: Default browser margins on SVG elements caused the viewport's scrollHeight to be 8005px instead of 8000px, creating extra scrollable area beyond the canvas.

Solution:

  • Added explicit padding: 0 and margin: 0 to #red-ui-workspace-chart container
  • Added display: block, margin: 0, and padding: 0 to SVG element via #red-ui-workspace-chart > svg selector
  • The display: block prevents inline element spacing issues

Files Changed:

  • workspace.scss:41-42, 52-57 - Added margin/padding resets for container and SVG

Result: Canvas now has exact 8000px scrollable area with no grey padding visible at bottom.

Spacebar Hold Scrolling Bug

Issue: When holding spacebar down, the canvas would move down unexpectedly, making the space+scroll interaction buggy.

Root Cause: The preventDefault() was only called on the first spacebar keydown event. When spacebar is held, browsers fire repeated keydown events. After the first keydown set spacebarPressed = true, subsequent keydown events weren't prevented because the condition e.type === "keydown" && !spacebarPressed failed, allowing browser's default space-scroll behavior.

Solution:

  • Moved preventDefault() and stopPropagation() outside the conditional checks
  • Now blocks ALL spacebar events (both keydown repeats and keyup), not just the first keydown

Files Changed:

  • view.js:611-619 - Restructured spacebar event handler to always prevent default

Result: Holding spacebar no longer causes unwanted canvas scrolling.

Minimap Auto-Show Behavior

Issue: Minimap was showing on selection changes and when entering pan mode (before actual panning), causing unnecessary flashing.

Solution:

  • Removed view:selection-changed event listener - minimap no longer shows when selecting nodes
  • Removed view:navigate emissions from pan mode entry points (touch long-press, spacebar+click, middle-click)
  • Added view:navigate emission to regular touchpad scroll handler for consistent behavior
  • Kept emissions only during actual panning movement and zooming

Files Changed:

  • view-navigator.js:195-198 - Removed selection-changed listener
  • view.js:483, 1529, 1539 - Removed navigate events from pan mode entry
  • view.js:876 - Added navigate event to touchpad scroll handler

Result: Minimap now appears only during actual panning (touchpad or mouse) and zooming, not on selection or pan mode entry.

Diagonal Trackpad Panning

Issue: Trackpad scrolling was restricted to horizontal OR vertical movement, not both simultaneously.

Root Cause: Browser's native scroll behavior on overflow: auto containers locks into one axis at a time, even before JavaScript wheel events fire.

Solution:

  • Added evt.preventDefault() and evt.stopPropagation() to regular scroll handling
  • Manually apply both deltaX and deltaY to scrollLeft/scrollTop simultaneously
  • Prevents browser's axis-locked scroll behavior from taking over
  • Also updated CSS touch-action from pan-x pan-y to manipulation (though this primarily affects touch events, not trackpad)

Files Changed:

  • view.js:864-890 - Added manual diagonal scroll handling
  • base.scss:22, 33 - Changed touch-action to manipulation

Result: Trackpad can now pan diagonally without axis-locking.

Known Issues & Future Work

To Be Tested

  • Comprehensive testing across different browser zoom levels (100%, 125%, 150%, 200%)
  • Cross-browser testing (Chrome, Firefox, Safari, Edge)
  • Testing on different touchscreen devices (tablets, touch-enabled laptops)
  • Testing with different trackpad sensitivities and gesture settings
  • Diagonal trackpad panning (fixed)

Potential Improvements

  • Additional fine-tuning of zoom speeds and acceleration curves based on user feedback
  • Consider adding keyboard shortcuts for zoom reset (Ctrl+0 / Cmd+0)
  • Evaluate need for custom zoom level indicator in UI
  • Consider adding preferences for zoom/pan sensitivity

Edge Cases to Monitor

  • Behavior when canvas content is very small or very large
  • Interaction with browser accessibility features
  • Performance with extremely large flows (100+ nodes)
  • Multi-monitor scenarios with different DPI settings

Testing Checklist

When verifying canvas interaction improvements:

  1. Zoom Testing

    • Mouse wheel zoom in/out
    • Alt+scroll zoom (keyboard modifier)
    • Space+scroll zoom (keyboard modifier)
    • Trackpad pinch gesture (spread = zoom in, pinch = zoom out, generates Ctrl+wheel)
    • Touch screen pinch gesture (direct touch events)
    • UI zoom buttons (zoom in, zoom out, reset) - smooth animated, focal point locking
    • Zoom-to-fit button (shows all nodes with padding, respects max zoom of 1.0)
    • Zoom-to-fit hotkey (Ctrl+1 / Cmd+1)
    • Zoom hotkeys (Ctrl+Plus, Ctrl+Minus, Ctrl+0)
    • Zoom focal point stays on viewport center for button/hotkey zooms
    • Dynamic zoom limits prevent empty space
    • Maximum zoom capped at 1.0 (100%)
    • Animation duration scales with zoom distance (200-350ms)
    • Sequential zooms maintain same focal point (1 second timeout)
    • Zoom-to-fit works immediately after page load
    • Pan animation when zoom-to-fit doesn't need zoom change
  2. Pan Testing

    • Two-finger pan on trackpad/touch
    • Diagonal panning works (not axis-locked)
    • Spacebar+click pan on desktop
    • Middle-click pan on desktop
    • Momentum scrolling with edge bounce
    • No lag when switching between pan and zoom
  3. UI/UX Testing

    • Minimap auto-shows on flow startup and workspace changes
    • Minimap auto-shows during panning and zooming
    • Minimap auto-shows during zoom button/hotkey/zoom-to-fit
    • Minimap does not show on selection changes
    • Minimap fades after 2 seconds
    • No scrollbars visible on canvas
    • No pinch-zoom on UI elements
    • Gesture state cleanup on cursor exit
    • Grab cursor (open hand) shows when spacebar held
    • Grabbing cursor (closed hand) shows during active pan (spacebar or middle-click)
    • No grey padding visible at canvas bottom
  4. Browser Zoom Testing

    • Test at 100% browser zoom
    • Test at 125% browser zoom
    • Test at 150% browser zoom
    • Test at 200% browser zoom
    • Verify all interactions work at each zoom level

Files Modified

Key files involved in canvas interaction improvements:

  • packages/node_modules/@node-red/editor-client/src/js/ui/view.js - Main view controller
  • packages/node_modules/@node-red/editor-client/src/js/ui/view-zoom-animator.js - Zoom animations
  • packages/node_modules/@node-red/editor-client/src/js/ui/view-zoom-constants.js - Zoom configuration
  • packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js - Minimap controller
  • packages/node_modules/@node-red/editor-client/src/sass/workspace.scss - Canvas styling
  • packages/node_modules/@node-red/editor-client/src/sass/base.scss - Base UI styling
  • Gruntfile.js - Build configuration

Commit History

Interaction improvements (20 commits total on claude/issue-44-20250925-0754):

Session 1: Previous commits (commits 1-13)

  1. e7a028b - feat: Add enhanced zoom and scroll features
  2. bdfa06b - Implement smooth zoom functionality with pinch-to-zoom support
  3. 37f9786 - Fix trackpad zoom direction - spreading fingers now zooms in
  4. e42b09d - Fix zoom focal point stability at canvas edges
  5. a12b65b - Improve zoom smoothness and control
  6. feec7ec - Add two-finger panning and spacebar+click panning
  7. e0c5b84 - Prevent UI pinch-to-zoom while keeping canvas zoomable
  8. 121982e - Fix zoom gesture detection after two-finger panning
  9. 7918693 - Implement dynamic zoom limits to match canvas boundaries
  10. f13ed66 - Add dynamic minimum zoom recalculation on viewport resize
  11. 53dce6a - Hide scrollbars and add auto-show/hide minimap on navigation
  12. 875db2c - Enable diagonal trackpad panning by preventing axis-locked scroll
  13. (previous) - Improve minimap auto-show behavior to only trigger during actual navigation

Session 2: Zoom button/hotkey improvements (commits 14-20)

  1. ad00ca23e - Add scroll spacer to fix scrollable area at minimum zoom
  2. 48f0f3be - Fix minimap viewport position at non-1.0 zoom levels
  3. be1da360 - Fix grey padding at canvas bottom by resetting SVG margins
  4. 6f164a8a - Add zoom-to-fit button to show all nodes at once
  5. 7fdff0ca - Fix zoom button handlers - zoom in/out were reversed
  6. e46cfc94 - Move zoom-to-fit button between reset and zoom-in
  7. 95304e26 - Revert "Move zoom-to-fit button between reset and zoom-in"
  8. 788d0a38 - Add Ctrl+1/Cmd+1 keyboard shortcut for zoom-to-fit
  9. 5c090786 - Remove animation from zoom buttons for instant, smooth zooming
  10. 6e49e962 - Fix viewport drift when using zoom buttons without focal point
  11. e3de29d8 - Fix zoom center calculation to use oldScaleFactor consistently
  12. 935ff622 - Fix zoom button animation and improve performance
  13. f07907e1 - Add focal point locking for sequential button/hotkey zooms
  14. 0247a910 - Add minimap auto-show for zoom button/hotkey interactions
  15. 62612139 - Add grab/grabbing cursor for spacebar pan mode
  16. ce5a0313 - Add grabbing cursor for middle-click pan mode
  17. 594c0d66 - Make zoom animation duration relative to maintain consistent velocity
  18. 45c2a798 - Set maximum zoom level to 1.0
  19. ed71cf91 - Fix zoom-to-fit to properly center nodes in viewport
  20. 732c8283 - Refresh active nodes before zoom-to-fit to work immediately (includes pan animation matching zoom duration, 200-350ms range)
  21. 5e056a4d - Add minimap auto-show on flow startup and workspace changes