Files
ridebus-board/test/fixtures/journey-perf.js
Zakhar Timoshenko 823ab14367 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.
2026-10-11 15:13:05 +03:00

39 lines
2.1 KiB
JavaScript

// 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'] });