Skip to main content

Debounce Frontend Cursor Change after Releasing Mouse Button

When building highly interactive UIs, managing frequent updates is crucial for a smooth user experience. Rapid changes, especially from mouse movements, can lead to visual glitches like cursor flicker. This post demonstrates how we leveraged Reactive Programming principles and debouncing to eliminate this issue in DrawMotive canvas.

The Problem:

When draw lines by clicking and dragging the mouse, upon releasing the mouse button (mouseup event), the cursor would often flicker. This flicker was caused by slight mouse movements immediately occurring after the mouseup, triggering rapid and unnecessary cursor changes.

Change Cursor Flicker

(demonstrates the cursor flicker before the fix).

Previous Approach (Direct Callback):

We initially employed a direct callback mechanism for cursor updates.

The Debouncing Solution (Reactive Programming):

To address the flicker, we embraced a Reactive Programming model using Rx (Reactive Extensions):

  1. Reactive Observable for Cursor Style: We introduced a reactive property to represent the desired cursor style:

    // before
    public event Extensions.UpdateCursorDelegate? UpdateCursor;

    // after
    private readonly Subject<CursorStyle> _updateCursorSubject = new();
    public Observable<CursorStyle> UpdateCursorObservable { get; init; }

    // `ThrottleLast` operator to the `CursorStyle` property to debounce the updates
    UpdateCursorObservable = _updateCursorSubject.AsObservable().ThrottleLast(TimeSpan.FromMilliseconds(30));

    This code snippet ensures that only the last emitted cursor style within a 30-millisecond window is processed. Rapid changes are effectively ignored, preventing the flicker.

  2. Updating the Cursor Style: Whenever a cursor change is required, we simply push the new CursorStyle value onto the subject:

    // before
    UpdateCursor?.Invoke(cursor);

    // after
    _updateCursorSubject.OnNext(newCursorStyle);
  3. Subscribing to the Observable: Event subscribers were updated to use the observable:

    // Before
    Scene.UpdateCursor += OnUpdateCursor;

    // After
    Scene.UpdateCursorObservable.Subscribe(OnUpdateCursor);

Results:

By integrating debouncing through ThrottleLast, we successfully eliminated the cursor flicker, resulting in a significantly smoother and more polished user experience.

Change Cursor Flicker

(demonstrates the improved behavior after implementing debouncing).

By leveraging Reactive Programming principles and Rx's ThrottleLast operator, we can implement a clean and effective solution to the cursor flicker problem.