From 5d24f8c3a39b57e54280ef89469f0cad5e4ba4d9 Mon Sep 17 00:00:00 2001 From: 4aiman Date: Tue, 22 Sep 2026 11:31:37 +0300 Subject: [PATCH] Keep scroll position across same-screen re-renders (day nav, add food) instead of jumping to top --- frontend/js/main.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/frontend/js/main.js b/frontend/js/main.js index 46f5256..abae94d 100644 --- a/frontend/js/main.js +++ b/frontend/js/main.js @@ -220,6 +220,32 @@ async function init() { // Router // --------------------------------------------------------------------------- +// Keep the vertical scroll position across a re-render of the SAME screen (day nav, adding +// food, deletes) instead of jumping to the top. Anchor on the topmost visible top-level +// section + its offset (robust to height changes, e.g. one more food row or async charts). +function captureScrollAnchor(el) { + let top = el.scrollTop, kids = el.children, idx = 0, delta = top; + for (let i = 0; i < kids.length; i++) { + if (kids[i].offsetTop <= top + 4) { idx = i; delta = top - kids[i].offsetTop; } else break; + } + return { idx, delta }; +} +function applyScrollAnchor(el, a) { + let kid = el.children[a.idx]; + if (kid) el.scrollTop = Math.max(0, kid.offsetTop + a.delta); +} +// The screen fills asynchronously (charts, nutrient strips), so re-apply for a short window as +// content settles; stop early if the user scrolls themselves. +function scheduleScrollRestore(el, a) { + let stop = false, off = () => { stop = true; }; + el.addEventListener("wheel", off, { passive: true, once: true }); + el.addEventListener("touchmove", off, { passive: true, once: true }); + let apply = () => { if (!stop) applyScrollAnchor(el, a); }; + apply(); + requestAnimationFrame(apply); + [60, 180, 400].forEach(ms => setTimeout(apply, ms)); +} + function renderScreen(name, fromPopstate) { // Release screen wake lock and compass listener when leaving recording screen if (window._screenLock) { try { window._screenLock.release(); } catch {} window._screenLock = null; } @@ -243,6 +269,12 @@ function renderScreen(name, fromPopstate) { supplements: renderSupplements, settings: renderSettings, }; + // Re-render of the same screen: remember where we were scrolled so we can stay put. + let scrollAnchor = null; + if (prevScreen === name) { + let oldContent = app.querySelector(".screen-content"); + if (oldContent && oldContent.scrollTop > 0) scrollAnchor = captureScrollAnchor(oldContent); + } app.innerHTML = ""; // Floating recording bar (visible on all screens except recording) @@ -271,6 +303,7 @@ function renderScreen(name, fromPopstate) { content.className = "screen-content"; app.appendChild(content); screens[name](content); + if (scrollAnchor) scheduleScrollRestore(content, scrollAnchor); } function buildNav(active) {