perf(mobile): skip redundant end-of-slide re-render (unchanged content) + load-older before rubber-band
Device log: slide is 60fps EXCEPT one ~60-130ms hitch at the END = the deferred renderThread rebuilding the (identical) DOM at slide end. Skip it when the network result matches the cache render (signature). Load-older: fired at scrollTop<120 = during the rubber-band over-scroll past the top (beforeScrollTop was -289), so the prepend+anchor jerked momentum. Trigger earlier (<700, skip negative scrollTop) so it loads while still scrolling and never interrupts the bounce. Build marker -> 2026-07-19-batch123. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+11
-3
@@ -1140,7 +1140,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="/icons.js?v=6"></script>
|
<script src="/icons.js?v=6"></script>
|
||||||
<script>window.__BUILD='2026-07-19-batch122';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
<script>window.__BUILD='2026-07-19-batch123';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
||||||
// Emoji are rendered with the OS's own (colour) emoji font — instant, zero network.
|
// Emoji are rendered with the OS's own (colour) emoji font — instant, zero network.
|
||||||
//
|
//
|
||||||
// We used to run Twemoji over every emoji, which swapped each one for an <img> pulled INDIVIDUALLY from
|
// We used to run Twemoji over every emoji, which swapped each one for an <img> pulled INDIVIDUALLY from
|
||||||
@@ -2700,7 +2700,7 @@ function updateBubble(m){
|
|||||||
// Floating date pill (updates to the day at the top of the viewport) + jump-to-latest button.
|
// Floating date pill (updates to the day at the top of the viewport) + jump-to-latest button.
|
||||||
function onMsgsScroll(){
|
function onMsgsScroll(){
|
||||||
const box=document.getElementById('msgs'); if(!box) return;
|
const box=document.getElementById('msgs'); if(!box) return;
|
||||||
if(box.scrollTop < 120 && _hasMoreOlder && !_loadingOlder && Date.now()>_olderCd) loadOlder(); // near the top → pull in older history
|
if(box.scrollTop < 700 && box.scrollTop >= 0 && _hasMoreOlder && !_loadingOlder && Date.now()>_olderCd) loadOlder(); // pull older history EARLY (while still scrolling down, before the rubber-band top) so the prepend+anchor never interrupts momentum; skip negative (rubber-band) scrollTop
|
||||||
const nearBottom=(box.scrollHeight - box.scrollTop - box.clientHeight) < 120;
|
const nearBottom=(box.scrollHeight - box.scrollTop - box.clientHeight) < 120;
|
||||||
const jl=document.getElementById('jumpLatest'); if(jl) jl.style.display=nearBottom?'none':'grid';
|
const jl=document.getElementById('jumpLatest'); if(jl) jl.style.display=nearBottom?'none':'grid';
|
||||||
const fd=document.getElementById('floatDate'); if(!fd) return;
|
const fd=document.getElementById('floatDate'); if(!fd) return;
|
||||||
@@ -2908,10 +2908,18 @@ async function openConvo(kind,id){
|
|||||||
const inp0=document.getElementById('msgInput'); if(inp0 && !isMobileUA()) inp0.focus(); // #2: don't auto-open the phone keyboard on chat open
|
const inp0=document.getElementById('msgInput'); if(inp0 && !isMobileUA()) inp0.focus(); // #2: don't auto-open the phone keyboard on chat open
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const _sig=(a)=>((a&&a.length)||0)+'|'+(((a&&a[a.length-1])||{}).id||'')+'|'+(((a&&a[a.length-1])||{}).edited_at||'');
|
||||||
|
const _prevSig = _convoRevealed ? _sig(THREAD) : null; // signature of what the cache render already shows (before we overwrite)
|
||||||
THREAD=msgs;
|
THREAD=msgs;
|
||||||
THREAD_CACHE.set(ckey, THREAD.slice());
|
THREAD_CACHE.set(ckey, THREAD.slice());
|
||||||
_hasMoreOlder = msgs.length >= PAGE; _loadingOlder=false; // a full page back means there may be older history
|
_hasMoreOlder = msgs.length >= PAGE; _loadingOlder=false; // a full page back means there may be older history
|
||||||
afterOpenSlide(()=>{ if(selected&&selected.kind===kind&&selected.id===id) renderThread(); }); // defer the network re-render past the slide (no 40-bubble innerHTML into the compositing layer mid-transform)
|
// If the network result matches what the cache render already painted, DON'T rebuild the DOM at slide
|
||||||
|
// end (that innerHTML+paint was the ~60-130ms hitch the device log showed). Just keep it pinned.
|
||||||
|
if(_prevSig!==null && _sig(msgs)===_prevSig){
|
||||||
|
afterOpenSlide(()=>{ if(!(selected&&selected.kind===kind&&selected.id===id)) return; const b=document.getElementById('msgs'); if(b && !b.querySelector('.new-sep') && (b.scrollHeight-b.scrollTop-b.clientHeight)<1200) b.scrollTop=b.scrollHeight; });
|
||||||
|
} else {
|
||||||
|
afterOpenSlide(()=>{ if(selected&&selected.kind===kind&&selected.id===id) renderThread(); }); // content changed → re-render, but only after the slide
|
||||||
|
}
|
||||||
if(kind==='dm') syncDmRowTick(id); // #1: keep the sidebar tick consistent with the loaded thread
|
if(kind==='dm') syncDmRowTick(id); // #1: keep the sidebar tick consistent with the loaded thread
|
||||||
// #3: if there were unread messages, drop a "New messages" divider, scroll to the first unread,
|
// #3: if there were unread messages, drop a "New messages" divider, scroll to the first unread,
|
||||||
// and show the "jump to newest" arrow so the user can return to the bottom.
|
// and show the "jump to newest" arrow so the user can return to the bottom.
|
||||||
|
|||||||
Reference in New Issue
Block a user