Eraser (Erase All) stutters/glitches while dragging — unthrottled per-mousemove scan #223
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_whiteboard#223
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Holding the eraser and dragging it across the board causes visible stutter/flicker ("glitching"). It happens in the default "Erase All" eraser mode.
Steps to reproduce
Root cause
In "Erase All" mode the erase runs unthrottled on every
mousemove:crates/hero_whiteboard_admin/static/web/js/whiteboard/tools.js,onMouseMove(~line 779-787) callseraseAtPosition(pos)directly on every mousemove event.eraseAtPosition(~line 1257) does a full pass each call:layer.find('.object')(whole-layer traversal) plusgetClientRect()per object, per-segment circle hit-tests, a connectors scan, a comments scan, andlayer.batchDraw().Running that on every mouse event (often 100+/sec) can't keep up with pointer movement, so the redraws stutter and flicker.
The precision eraser mode does not have this problem because it is already
requestAnimationFrame-throttled and interpolates erase points between frames (_pushEraserCircleAndSchedule/_applyEraserCutsForDrag). The default "all" mode never got the same treatment.Proposed fix
Throttle the "all" mode erase to one pass per animation frame, mirroring the precision path:
requestAnimationFramethat runseraseAtPositiononce per frame.Optionally remove the redundant double
updateEraserCursorcall (it runs both inonMouseMoveand in the dedicatedmousemove.eraser_cursorhandler).Acceptance criteria
Implementation Spec for Issue #223
Objective
Make "Erase All" eraser dragging smooth by throttling its work to one pass per animation frame, while still erasing everything along a fast drag path.
Root cause
onMouseMovecallseraseAtPosition(pos)on every mousemove in "all" mode;eraseAtPositiondoes a full-layer scan + per-object getClientRect + connector/comment scans + batchDraw each call. Unthrottled, this stutters. Precision mode is already rAF-throttled and interpolated.Files to Modify
crates/hero_whiteboard_admin/static/web/js/whiteboard/tools.js-onMouseMove(all-mode branch),onMouseUp/gesture-end, plus a small new rAF scheduler.Implementation Plan
Step 1: Add an rAF-throttled, interpolated "all" eraser scheduler
Files:
crates/hero_whiteboard_admin/static/web/js/whiteboard/tools.jseraseAllLatest(last pointer pos),eraseAllScheduled(bool),eraseAllLastApplied(last processed pos)._pushEraseAllAndSchedule(pos): storeeraseAllLatest = pos; if not already scheduled, set scheduled andrequestAnimationFrame(_applyEraseAllForDrag)._applyEraseAllForDrag(): clear the scheduled flag; build a chain of points fromeraseAllLastApplied(or latest if null) toeraseAllLatestusing stridemax(1, worldRadius * 0.5); call the existingeraseAtPosition(point)for each point in order; seteraseAllLastApplied = eraseAllLatest. This reuses the existing erase logic but at most once per frame and without gaps on fast moves.Dependencies: none
Step 2: Route mousemove through the scheduler and flush on release/start
Files:
crates/hero_whiteboard_admin/static/web/js/whiteboard/tools.jsonMouseMove, theeraserbranch: keepupdateEraserCursor(pos); whenisErasingand mode isall, call_pushEraseAllAndSchedule(pos)instead oferaseAtPosition(pos).allmode, reseteraseAllLastApplied = nulland erase the initial point immediately (eraseAtPosition(pos)) so a single click still erases.onMouseUp(and any gesture-abort path) for the eraser inallmode: if a frame is pending, run_applyEraseAllForDrag()once so the final position is always processed, then reset the scheduler state.Dependencies: Step 1
Step 3 (optional, low risk): de-duplicate cursor updates
Files:
crates/hero_whiteboard_admin/static/web/js/whiteboard/tools.jsupdateEraserCursoris invoked both inonMouseMoveand the dedicatedmousemove.eraser_cursorhandler. Keep a single source (the dedicated handler) to avoid a redundant uiLayer batchDraw per move. Only do this if it does not change cursor-visibility behaviour.Dependencies: none
Acceptance Criteria
Notes
eraseAtPositionunchanged; only its call cadence changes.node --checkplus a manual drag test.Implementation Summary
Changes
crates/hero_whiteboard_admin/static/web/js/whiteboard/tools.js:_pushEraseAllAndSchedulerecords the latest pointer position and schedules onerequestAnimationFrame;_applyEraseAllForDragruns at most once per frame, stepping along the path from the last processed point to the latest (stride = half the eraser radius) and calling the existingeraseAtPositionfor each step;_flushEraseAllprocesses any pending frame and resets the state.onMouseMove(eraser branch) now calls_pushEraseAllAndSchedule(pos)instead of runningeraseAtPositionon every event.eraseAllLastApplied.onMouseUp(and the buttons===0 abort path that routes through it) flushes the pending frame so the final position is always erased.updateEraserCursorcall inonMouseMove; the dedicatedmousemove.eraser_cursorhandler (active for the whole eraser tool lifecycle) already tracks the cursor.eraseAtPositionitself is unchanged — only how often it runs. Precision mode is untouched.Verification
node --checkon tools.js passes.Acceptance criteria
Root cause (corrected) and final fix
The visible "glitch" was not erase throughput — it reproduced on a completely empty board. The eraser sets the container
cursor: noneand drew the red circle on the canvas (Konva), so the circle was the only visible cursor and trailed the real pointer by a frame or two through Konva's batchDraw pipeline. Slow moves hid it; fast moves made it visibly lag/jump.Fix
setToolCursorbuilds it sized to the eraser radius (5-50 => <=104px, within the browser cursor-size limit) with acrosshairfallback; the size updates live with the eraser-size slider. The canvas-drawn circle is no longer created.Also included (erase-pass smoothness on heavy boards)
Verification
node --checkpasses.Acceptance criteria