feat: performance improvements

This commit is contained in:
Johannes Millan 2025-10-24 13:25:24 +02:00
parent fddedf3fa6
commit a2639532a0
3 changed files with 39 additions and 19 deletions

View file

@ -23,7 +23,8 @@ import { Task } from '../../../features/tasks/task.model';
import { WorkContext } from '../../../features/work-context/work-context.model';
import { TaskService } from '../../../features/tasks/task.service';
import { PomodoroService } from '../../../features/pomodoro/pomodoro.service';
import { Subscription } from 'rxjs';
import { animationFrameScheduler, Subscription } from 'rxjs';
import { distinctUntilChanged, observeOn } from 'rxjs/operators';
@Component({
selector: 'play-button',
@ -333,19 +334,25 @@ export class PlayButtonComponent implements OnInit, OnDestroy {
// Subscribe to task progress for circle animation
this._subs.add(
this.taskService.currentTaskProgress$.subscribe((progressIN) => {
const circleSvgEl = this.circleSvg()?.nativeElement;
if (circleSvgEl) {
let progress = progressIN || 0;
if (progress > 1) {
progress = 1;
this.taskService.currentTaskProgress$
.pipe(
// Align ring updates with the frame budget and skip duplicate ratios.
observeOn(animationFrameScheduler),
distinctUntilChanged(),
)
.subscribe((progressIN) => {
const circleSvgEl = this.circleSvg()?.nativeElement;
if (circleSvgEl) {
let progress = progressIN || 0;
if (progress > 1) {
progress = 1;
}
// Calculate dashoffset: 0 when 0%, negative circumference when 100%
// This shows the completed portion of the circle
const dashOffset = this.circumference * -progress;
this._renderer.setStyle(circleSvgEl, 'stroke-dashoffset', dashOffset);
}
// Calculate dashoffset: 0 when 0%, negative circumference when 100%
// This shows the completed portion of the circle
const dashOffset = this.circumference * -progress;
this._renderer.setStyle(circleSvgEl, 'stroke-dashoffset', dashOffset);
}
}),
}),
);
}

View file

@ -10,8 +10,8 @@ import {
Renderer2,
viewChild,
} from '@angular/core';
import { fromEvent, Subscription } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { animationFrameScheduler, fromEvent, Subscription } from 'rxjs';
import { observeOn, takeUntil } from 'rxjs/operators';
import { isTouchOnly } from '../../../util/is-touch-only';
import { MatFabButton } from '@angular/material/button';
import { MatIcon } from '@angular/material/icon';
@ -80,7 +80,11 @@ export class SplitComponent implements AfterViewInit {
this.eventSubs = touchend$.subscribe(() => this.onMoveEnd());
const touchmove$ = fromEvent(document, 'touchmove')
.pipe(takeUntil(touchend$))
.pipe(
takeUntil(touchend$),
// Run drag calculations at most once per frame to avoid layout thrash.
observeOn(animationFrameScheduler),
)
.subscribe((e: Event) => this.onMove(e as TouchEvent));
this.eventSubs.add(touchmove$);
@ -92,7 +96,11 @@ export class SplitComponent implements AfterViewInit {
this.eventSubs = mouseup$.subscribe(() => this.onMoveEnd());
const mousemove$ = fromEvent(document, 'mousemove')
.pipe(takeUntil(mouseup$))
.pipe(
takeUntil(mouseup$),
// Run drag calculations at most once per frame to avoid layout thrash.
observeOn(animationFrameScheduler),
)
.subscribe((e: Event) => this.onMove(e as MouseEvent));
this.eventSubs.add(mousemove$);

View file

@ -19,6 +19,7 @@ import { LayoutService } from '../../core-ui/layout/layout.service';
import { TakeABreakService } from '../take-a-break/take-a-break.service';
import { ActivatedRoute } from '@angular/router';
import {
animationFrameScheduler,
from,
fromEvent,
Observable,
@ -28,7 +29,7 @@ import {
zip,
} from 'rxjs';
import { TaskWithSubTasks } from '../tasks/task.model';
import { delay, filter, map, switchMap } from 'rxjs/operators';
import { delay, filter, map, observeOn, switchMap } from 'rxjs/operators';
import { fadeAnimation } from '../../ui/animations/fade.ani';
import { PlanningModeService } from '../planning-mode/planning-mode.service';
import { T } from '../../t.const';
@ -156,7 +157,11 @@ export class WorkViewComponent implements OnInit, OnDestroy {
filter((isChanging) => !isChanging),
delay(50),
switchMap(() => this.splitTopEl$),
switchMap((el) => fromEvent(el, 'scroll')),
switchMap((el) =>
// Defer scroll reactions to the next frame so layoutService.isScrolled
// toggles happen in sync with the browser repaint.
fromEvent(el, 'scroll').pipe(observeOn(animationFrameScheduler)),
),
);
private _subs: Subscription = new Subscription();