Frontmatter
| id | 8578 |
| title | Improve wheel event performance with passive listeners |
| state | Closed |
| labels | enhancementaiperformance |
| assignees | tobiu |
| createdAt | Jan 12, 2026, 7:21 AM |
| updatedAt | Jan 12, 2026, 8:07 AM |
| githubUrl | https://github.com/neomjs/neo/issues/8578 |
| author | tobiu |
| commentsCount | 1 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [] |
| blocking | [] |
| closedAt | Jan 12, 2026, 8:07 AM |
Improve wheel event performance with passive listeners

Input from Gemini 3 Pro:
✦ I have implemented a hybrid event listening strategy to resolve the "Violation" warnings and improve global scrolling performance.
1. The Global Performance Fix
The global
wheelevent listener insrc/main/DomEvents.mjshas been switched to{ passive: true }.
- Impact: This tells the browser that the main thread will not prevent scrolling. The browser can now scroll the page immediately (on the compositor thread) without waiting for the main thread to process the event.
- Result: This eliminates the "Handling of 'wheel' input event was delayed..." warning and provides silky smooth scrolling for 95% of the application (Grids, Lists, standard scroll containers).
2. The Local Override (Hybrid Strategy)
For components that must control the wheel (e.g., to map scroll deltas to 3D rotation or zoom), we cannot use a passive listener because we need
preventDefault().
- Implementation: I enhanced
src/manager/DomEvent.mjs(App Worker) to support configurable listener options (passive,capture,local) and pass them to the Main Thread.- Component Updates: Updated
Helix,Gallery,DateSelector,YearComponent, andCircleto use specific local listener configurations:wheel: { fn : me.onMouseWheel, local : true, passive: false, // Explicitly opt-out of passive mode bubble : false // Prevent logical bubbling to parent containers }- Bug Fix: I also implemented a
handlerMapinDomEvent.mjsto ensure local listeners map to their correct specialized handlers (e.g.,onWheel,onChange) instead of the genericdomEventListener. This incidentally fixed a criticalRangeError(infinite loop) in form fields by ensuring the correct event signature and payload were used.3. Why the "New" Warning is Good
You may now see a specific warning in the console when interacting with
HelixorGallery:[Violation] Added non-passive event listener to a scroll-blocking 'wheel' event.This is a success indicator, not a failure.
- Previously: We paid the "scroll-blocking tax" globally on
document.body, affecting every single scroll interaction in the app.- Now: We have moved this tax only to the specific DOM nodes of components that actually require
preventDefault().- The warning confirms that we have successfully isolated the cost. The rest of the application remains unburdened and performant.
4. Logic Isolation
By setting
bubble: falseon these local listeners, we ensure that the wheel event is consumed entirely by the component (e.g., spinning the Helix) and does not logically bubble up to scroll parent containers, fixing the regression where the parent page would scroll simultaneously.
Switch the global
wheelevent listener inDomEvents.mjstopassive: trueto prevent main thread blocking during scrolling. UpdateDomEvents.mjs(Main Thread) andDomEvent.mjs(Manager) to support local DOM listeners with configurable options (e.g.passive: false,capture: true) to allow specific components (Gallery, Helix) to opt-out of the passive default.