mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
perf(dashboard): fill the hero rotation dot on the compositor (#1758)
* perf(dashboard): fill the hero rotation dot on the compositor The active hero dot animated `width` 0 → 18px for every 8 s rotation, so an idle dashboard with two or more slides ran style, layout and paint on every frame. The fill is now a full-width bar that slides in with `transform` under the pill's rounded clip. The `animationend` advance, the pause and reduced-motion behaviour are unchanged. Measured on the E2E build (visible, four slides, 120 s): layouts 10,405-10,677 -> 366-369, renderer process CPU 14.2-16.4 s -> 3.7-4.1 s, GPU process CPU 14.8-18.8 s -> 14.0-14.8 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(e2e): drive the real hero rotation animation The unit specs dispatch `animationend` on the dot span by hand, so a fill whose real event no longer reached the handler would still pass. The new Electron spec shortens `--hero-rotation-ms` and checks that the running `::before` fill advances the slide, that pause holds it and that Play resumes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
6 files changed
+141
-8
No files matched your search
@@ -552,28 +552,41 @@
|
||||
width: 0;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: var(--hero-heading);
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
|
||||
// The fill is a full-width bar that slides in from the left under the
|
||||
// pill's rounded clip. Only `transform` animates, so the compositor runs
|
||||
// it: animating `width` meant style, layout and paint on every frame for
|
||||
// as long as an idle dashboard stayed on screen. Its `animationend`
|
||||
// reaches the span (the originating element) and advances the slide.
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: var(--hero-heading);
|
||||
}
|
||||
|
||||
.hero__dot--active & {
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
.hero--rotating .hero__dot--active & {
|
||||
.hero--rotating .hero__dot--active &::before {
|
||||
animation: hero-dot-fill var(--hero-rotation-ms, 8000ms) linear both;
|
||||
}
|
||||
|
||||
.hero--paused .hero__dot--active & {
|
||||
.hero--paused .hero__dot--active &::before {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes hero-dot-fill {
|
||||
from {
|
||||
width: 0;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
to {
|
||||
width: 18px;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { signal } from '@angular/core';
|
||||
import { TestBed, type ComponentFixture } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
@@ -276,3 +278,27 @@ describe('DashboardHeroComponent', () => {
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('DashboardHeroComponent rotation animation', () => {
|
||||
const styles = readFileSync(
|
||||
resolve(__dirname, 'dashboard-hero.component.scss'),
|
||||
'utf8'
|
||||
);
|
||||
const keyframes = (name: string) =>
|
||||
new RegExp(`@keyframes ${name}\\s*\\{([\\s\\S]*?)\\n\\}`).exec(
|
||||
styles
|
||||
)?.[1] ?? '';
|
||||
|
||||
// The fill runs for the whole time an idle dashboard is on screen: a
|
||||
// layout property here means style, layout and paint on every frame.
|
||||
it('fills the active dot with a compositor-only animation', () => {
|
||||
const frames = keyframes('hero-dot-fill');
|
||||
const properties = Array.from(
|
||||
frames.matchAll(/^\s*([a-z-]+)\s*:/gm),
|
||||
(match) => match[1]
|
||||
);
|
||||
|
||||
expect(properties.length).toBeGreaterThan(0);
|
||||
expect(new Set(properties)).toEqual(new Set(['transform']));
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user