Skip to content

fix: reduce live view scroll sensitivity#188

Open
hiroTamada wants to merge 6 commits intomainfrom
fix/live-view-scroll-sensitivity
Open

fix: reduce live view scroll sensitivity#188
hiroTamada wants to merge 6 commits intomainfrom
fix/live-view-scroll-sensitivity

Conversation

@hiroTamada
Copy link
Contributor

@hiroTamada hiroTamada commented Mar 24, 2026

Checklist

  • A link to a related issue in our repository
  • A description of the changes proposed in the pull request.
  • @mentions of the person or team responsible for reviewing proposed changes.

Summary

Users (Nava, Gabe) reported that live view scrolling is too sensitive/fast. The root cause is that the old scroll handler clamped raw pixel deltas directly to the scroll setting (default 10), meaning a single mouse wheel notch (~100px) would send 10 XTestFakeButtonEvent clicks to Chromium — each scrolling ~120px, totaling ~1200px per notch.

This PR replaces the old throttle-and-clamp approach with a fixed-rate polling scroll handler that:

  • Caps at 1 tick per direction per 100ms interval — regardless of how fast the user scrolls
  • Accumulates deltas between intervals and only sends the direction (sign), not magnitude
  • Filters jitter with a 3px threshold to ignore trackpad noise
  • Auto-cleans the interval when scroll input stops

Before vs After

Metric Before After
Mouse wheel: 1 notch ~10 ticks → 1200px 1 tick → 120px
Trackpad: max rate ~100 ticks/sec ~10 ticks/sec
Max scroll speed ~12000 px/sec ~1200 px/sec

Also lowers the default scroll sensitivity setting from 10 to 3.

Files changed

  • images/chromium-headful/client/src/components/video.vue — new scroll handler
  • images/chromium-headful/client/src/store/settings.ts — lower default scroll value

Test plan

  • Build Docker image with build-docker.sh and run with run-docker.sh (ENABLE_WEBRTC=true)
  • Test mouse wheel scrolling — single notch should scroll ~3-5 lines, not half a page
  • Test trackpad scrolling — fast swipes should feel controlled, not teleporting
  • Test scroll invert setting still works
  • Test that scrolling stops cleanly (no residual scroll after lifting finger)

Made with Cursor


Note

Low Risk
Low risk UI input change limited to client-side wheel handling, but it may affect scrolling feel across devices (mouse vs trackpad) due to new delta-to-tick normalization.

Overview
Reduces live view scroll sensitivity by converting raw wheel pixel deltas into discrete “ticks” (normalized by a PIXELS_PER_TICK constant) before applying the existing scroll clamp and 100ms throttle.

This makes wheel events send smaller, more consistent step counts (including preserving direction via Math.sign) instead of clamping large pixel deltas directly to the configured scroll value.

Written by Cursor Bugbot for commit 08a3238. This will update automatically on new commits. Configure here.

Replace the old throttle-and-clamp scroll handler with a fixed-rate
polling approach that sends at most 1 scroll tick per 100ms interval
in each direction. This dramatically reduces scroll speed for both
mouse wheel and trackpad input:

- Mouse wheel: 1 notch now sends 1 tick (~120px) instead of 7-10 ticks
- Trackpad: max 10 ticks/sec instead of 60-100 ticks/sec
- Accumulated deltas are tracked with a jitter threshold to filter noise
- Interval auto-cleans when scroll input stops

Also lower the default scroll sensitivity from 10 to 3.

Made-with: Cursor
The scroll slider (1–100) now controls the polling interval:
- scroll=1  → 200ms interval (5 ticks/sec, slowest)
- scroll=10 → 185ms interval (5.4 ticks/sec, default)
- scroll=50 → 117ms interval (8.5 ticks/sec)
- scroll=100 → 33ms interval (30 ticks/sec, fastest)

Restore default scroll to 10 (the original value) since the setting
is now meaningful again.

Made-with: Cursor
Reintroduce magnitude-aware tick calculation so fast scrolling feels
responsive. Accumulated pixel deltas are divided by 120 (one Chromium
scroll-notch worth of pixels) and clamped by the scroll setting.

Slow scroll (1 notch ≈ 100px) still produces 1 tick; fast scroll
(500px accumulated) now produces ~4 ticks instead of always 1.

Made-with: Cursor
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

const scaleAxis = (delta: number): number => {
if (Math.abs(delta) < JITTER_THRESHOLD) return 0
const ticks = delta / PIXELS_PER_TICK
const clamped = Math.min(Math.max(Math.round(ticks), -this.scroll), this.scroll)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing default scroll change undermines sensitivity fix

Medium Severity

The scaleAxis function clamps tick count to this.scroll, and the PR description states the default was lowered from 10 to 3 in settings.ts, but that companion change is missing from this commit. With the existing default of 10, scaleAxis allows up to 10 ticks per interval (~6480 px/sec max) instead of the intended 3 ticks (~1800 px/sec), largely defeating the purpose of capping trackpad scroll speed.

Fix in Cursor Fix in Web

Comment on lines +761 to +762
this.pendingScrollX += x
this.pendingScrollY += y
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The core issue here is that the old code clamped raw pixel deltas
(~100px per notch) directly as tick counts — that's the 10x multiplier bug. This can be fixed much more simply by dividing by 120 (pixels per Chromium
scroll notch) in the existing code path:

  x = x === 0 ? 0 : Math.min(Math.max(Math.round(x / 120) || Math.sign(x), -this.scroll), this.scroll)
  y = y === 0 ? 0 : Math.min(Math.max(Math.round(y / 120) || Math.sign(y), -this.scroll), this.scroll)

That keeps the existing throttle and avoids the interval/accumulation/cleanup
machinery. I wonder if starting with a more minimal fix and only adding complexity based on testing would be easier for us long term ^^

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — you're right that the core bug is just a missing divisor, and the interval/accumulation machinery is unnecessary complexity to solve it. Simplifying to your approach now.

Replace the interval/accumulation machinery with a one-line fix per
Sayan's review: divide raw pixel deltas by 120 (one Chromium scroll
notch) before clamping. Keeps the existing throttle, avoids interval
lifecycle complexity.

Old: 100px delta → clamped to 10 → 10 XButton events → 1200px scroll
New: 100px delta → 100/120 = 1 → 1 XButton event → 120px scroll
Made-with: Cursor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants