feat: add performance monitoring for journey transitions and optimize rendering
- Introduced a new markdown file documenting the optimizations made to the journey motion rendering, including changes to CSS and React components to improve performance on low-end devices. - Implemented a test-only JavaScript file to log layout work during automatic stop changes, tracking metrics such as name resizes, font writes, and heading replacements. - Enhanced the journey rendering by memoizing components and reducing unnecessary re-renders, aligning with web.dev recommendations for animations and layout thrashing prevention.
This commit is contained in:
38
test/fixtures/journey-perf.js
vendored
Normal file
38
test/fixtures/journey-perf.js
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Test-only observers: no prototype patches, production telemetry or state changes.
|
||||
// Logs layout work over 1200 ms after each automatic stop change, not target FPS.
|
||||
let focus = null, run = null;
|
||||
const boxes = new Set();
|
||||
const resize = new ResizeObserver(entries => {
|
||||
if (run) run.nameResizes += entries.filter(entry => boxes.has(entry.target)).length;
|
||||
});
|
||||
const mutations = new MutationObserver(records => {
|
||||
const cells = Array.from(document.querySelectorAll('.service-info[data-active="true"] .journey-station'));
|
||||
const next = cells.findIndex(cell => cell.getAttribute('data-focused') === 'true');
|
||||
if (next < 0) { focus = null; return; }
|
||||
if (focus !== null && focus !== next) {
|
||||
if (run) { clearTimeout(run.timer); finish(); }
|
||||
run = { from: focus + 1, to: next + 1, profile: document.querySelector('.board')?.getAttribute('data-profile'),
|
||||
nameResizes: 0, fontWrites: 0, headingReplacements: 0, started: performance.now(), timer: null };
|
||||
run.timer = setTimeout(finish, 1200);
|
||||
}
|
||||
focus = next;
|
||||
for (const box of document.querySelectorAll('.service-info[data-active="true"] .journey-station-name')) {
|
||||
if (!boxes.has(box)) { boxes.add(box); resize.observe(box); }
|
||||
}
|
||||
for (const box of boxes) if (!document.contains(box)) { resize.unobserve(box); boxes.delete(box); }
|
||||
if (!run) return;
|
||||
for (const record of records) {
|
||||
if (record.type === 'attributes' && record.attributeName === 'style'
|
||||
&& record.target.matches('.journey-station-name .destination-heading')) run.fontWrites++;
|
||||
if (record.type === 'childList' && record.target.matches('.journey-station-name')) {
|
||||
run.headingReplacements += Array.from(record.removedNodes).filter(node => node.nodeType === 1 && node.matches('.destination-heading')).length;
|
||||
}
|
||||
}
|
||||
});
|
||||
function finish() {
|
||||
if (!run) return;
|
||||
const { timer, started, ...result } = run;
|
||||
run = null;
|
||||
console.info('JOURNEY_PERF ' + JSON.stringify({ ...result, windowMs: Math.round(performance.now() - started) }));
|
||||
}
|
||||
mutations.observe(document.body, { subtree: true, childList: true, attributes: true, attributeFilter: ['data-focused', 'style'] });
|
||||
8
test/fixtures/night-server.mjs
vendored
8
test/fixtures/night-server.mjs
vendored
@@ -30,5 +30,11 @@ app.use(createApp(client,{weather:async(cityId,stopId)=>({cityId,stopId,updatedA
|
||||
app.use('/__fixture',express.urlencoded({extended:false}));
|
||||
app.post('/__fixture',(req,res)=>{if(scenarios[req.body.scenario]){scenario=req.body.scenario;started=Date.now();}if(req.body.alerts!==undefined)alertsOn=req.body.alerts==='true';if(req.body.expired!==undefined)expired=req.body.expired==='true';res.redirect(303,'/__fixture');});
|
||||
app.get('/__fixture',(_req,res)=>res.type('html').send(`<html lang="ru"><body style="font:20px system-ui;padding:32px"><h1>Тестовые сценарии ночного режима</h1><p>Текущий: ${scenario}; уведомление: ${alertsOn}; истёкший кеш: ${expired}</p><form method="post">${Object.keys(scenarios).map(s=>`<button style="padding:14px;margin:6px" name="scenario" value="${s}">${s}</button>`).join('')}<button name="alerts" value="true">Показать отмену</button><button name="alerts" value="false">Убрать отмену</button><button name="expired" value="true">Истёкший кеш</button><button name="expired" value="false">Свежий кеш</button></form><p>Часы приложения обновятся при опросе (5 секунд). После смены полного расписания обновите страницу табло.</p></body></html>`));
|
||||
const dist=fileURLToPath(new URL('../../dist/',import.meta.url));app.use(express.static(dist));app.get('/{*path}',(_req,res)=>res.sendFile(`${dist}/index.html`));
|
||||
// Optional local-only counters compare the saved and current production builds.
|
||||
const perf=process.env.JOURNEY_PERF==='1';
|
||||
const dist=process.env.BOARD_FIXTURE_DIST || fileURLToPath(new URL('../../dist/',import.meta.url));
|
||||
if(perf)app.get('/__journey-perf.js',(_req,res)=>res.type('js').send(readFileSync(fileURLToPath(new URL('./journey-perf.js',import.meta.url)),'utf8')));
|
||||
app.use(express.static(dist,{index:!perf}));app.get('/{*path}',(_req,res)=>perf
|
||||
? res.type('html').send(readFileSync(`${dist}/index.html`,'utf8').replace('</head>','<script defer src="/__journey-perf.js"></script></head>'))
|
||||
: res.sendFile(`${dist}/index.html`));
|
||||
await new Promise(resolve=>app.listen(5193,'127.0.0.1',resolve));console.log('Night fixture http://127.0.0.1:5193/ ; controls /__fixture');
|
||||
|
||||
Reference in New Issue
Block a user