Skip to content

perf: fix UI/scroll jank while browsing channels - #449

Open
jendrikmaass-tech wants to merge 1 commit into
Fredolx:mainfrom
jendrikmaass-tech:perf/browsing-jank
Open

perf: fix UI/scroll jank while browsing channels#449
jendrikmaass-tech wants to merge 1 commit into
Fredolx:mainfrom
jendrikmaass-tech:perf/browsing-jank

Conversation

@jendrikmaass-tech

Copy link
Copy Markdown

Problem

While browsing channels (before a stream is selected), the UI stutters — mouse/scroll lag on the channel grid. Once a stream is playing it's fine, because the grid isn't churning anymore. The root cause is Angular change detection running much more often than necessary during scroll and hover.

Changes

1. Move scroll handling out of the Angular zone + throttle with rAF (home.component.ts)
The old @HostListener("window:scroll") fired inside the Angular zone on every raw scroll event — each one read layout (scrollHeight/innerHeight, forcing a reflow) and triggered an app-wide change-detection pass. It's now registered via ngZone.runOutsideAngular with { passive: true }, throttled to one check per animation frame with requestAnimationFrame, and only re-enters the zone when the scroll-to-top button visibility flips or a new page needs loading. This removes the per-scroll-event CD storm that drove most of the stutter.

2. OnPush change detection on the channel tile (channel-tile.component.ts)
The tile is instantiated hundreds of times, so its CD cost is what compounds. With OnPush, tiles are skipped during unrelated CD passes (their @Inputs don't change while you move the mouse or scroll). markForCheck() is called at the exact points a tile mutates its own state (starting, favorite/fade, hidden, image load error, context-menu open) so those still repaint correctly. HomeComponent is intentionally left on default CD — it's a singleton mutating many fields from many callers, where OnPush would be high-risk/low-reward.

3. trackBy on the channel grid (home.component.html / home.component.ts)
So re-search / re-sort / load-more reuse existing tile DOM instead of destroying and rebuilding every tile.

4. Precompute sourceName (channel-tile.component.ts)
Replaces the getSourceName() method binding (a Map lookup evaluated every CD cycle) with a field computed once when the channel input is set.

5. Lazy-load channel logos (channel-tile.component.html)
loading="lazy" + decoding="async" so the WebView doesn't decode hundreds of remote images at once while scrolling.

Verification

  • ng build (AOT) passes — full TypeScript + template typecheck.
  • ✅ No test regression: the unit suite reports the same 25 failing / 4 passing both with and without these changes (the failures are pre-existing scaffold specs missing ngx-toastr test providers, unrelated to this PR).
  • ⚠️ Not benchmarked on Windows/WebView2 hardware — the changes are reasoned from the render hot path and build-verified. The scroll-zone fix (Improve tiles #1) should be the most noticeable improvement while scrolling a large list.

No behavior changes intended: infinite scroll, the scroll-to-top button, favorite/hide toggles, the context menu, and image error fallback all work as before.

The channel grid stuttered (mouse/scroll lag) while browsing, before a
stream was selected. Root cause was Angular change detection running far
more than necessary during scroll and hover.

- home: move window scroll handling out of the Angular zone and throttle
  it with requestAnimationFrame; only re-enter the zone when the
  scroll-to-top button flips or another page needs loading. Previously
  every scroll event forced a layout reflow and an app-wide change
  detection pass.
- channel-tile: use OnPush change detection so the (hundreds of) tiles
  are skipped during unrelated CD passes; markForCheck() is called at the
  points the tile mutates its own state.
- home: add trackBy to the channel *ngFor so re-search/re-sort/load-more
  reuses tile DOM instead of rebuilding every tile.
- channel-tile: precompute sourceName when the channel input is set
  instead of calling a method binding every CD cycle.
- channel-tile: lazy-load channel logos (loading=lazy, decoding=async).
@CLAassistant

CLAassistant commented Jul 25, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

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