polish(mobile): native-style parallax chat-open transition; diagnose scrollH balloon
- Transition: open now slides the whole .content pane in over the list with a subtle parallax + iOS easing (was an instant/panel-only slide). Close/back already parallax. - scrollH blip: added a spike watcher that reports the tallest element (class + img src tail) when the thread's per-message height balloons, so we can see what stretches then settles. Build marker -> 2026-07-18-batch113. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+20
-15
@@ -1081,15 +1081,18 @@
|
|||||||
body.kb-open{--sab:0px;}
|
body.kb-open{--sab:0px;}
|
||||||
/* Native-style push/pop: the conversation slides in from the right when opened and slides back out
|
/* Native-style push/pop: the conversation slides in from the right when opened and slides back out
|
||||||
when you go back, so navigation has a clear direction (like Teams/WhatsApp). */
|
when you go back, so navigation has a clear direction (like Teams/WhatsApp). */
|
||||||
@keyframes bzConvoIn{from{transform:translateX(100%);}to{transform:translateX(0);}}
|
/* Native-style push/pop (like iOS/Teams): the conversation (.content) slides in from the right while
|
||||||
@keyframes bzListIn{from{transform:translateX(-24%);}to{transform:translateX(0);}}
|
the chat LIST underneath slides with a subtle parallax. iOS spring-ish easing. */
|
||||||
body.chat-open:not(.chat-closing):not(.chat-dragging) .content .panel{animation:bzConvoIn .22s cubic-bezier(.2,.7,.3,1);}
|
@keyframes bzContentIn{from{transform:translateX(100%);}to{transform:translateX(0);}}
|
||||||
/* Close / interactive back (native push/pop, like Teams): reveal the chat LIST underneath with a
|
@keyframes bzListParallaxOut{from{transform:translateX(0);filter:brightness(1);}to{transform:translateX(-22%);filter:brightness(.97);}}
|
||||||
parallax slide-in while the conversation slides/drags off to the right. The conversation (.content)
|
@keyframes bzListParallaxIn{from{transform:translateX(-22%);filter:brightness(.97);}to{transform:translateX(0);filter:brightness(1);}}
|
||||||
is moved by an inline transform (showWelcome() for a tap, or the edge-drag gesture); the list
|
/* OPEN: content slides in over the list (list shown beneath, parallax out) */
|
||||||
(.chatcol) is force-shown beneath it for the duration. */
|
body.chat-opening .content{display:block!important;z-index:2;box-shadow:-12px 0 30px rgba(15,23,42,.18);will-change:transform;animation:bzContentIn .3s cubic-bezier(.32,.72,0,1);}
|
||||||
body.chat-closing .content, body.chat-dragging .content{display:block!important;z-index:2;box-shadow:-10px 0 26px rgba(15,23,42,.16);will-change:transform;}
|
body.chat-opening .chatcol{display:flex!important;position:absolute;inset:0;z-index:0;animation:bzListParallaxOut .3s cubic-bezier(.32,.72,0,1);}
|
||||||
body.chat-closing .chatcol{display:flex!important;position:absolute;inset:0;z-index:0;animation:bzListIn .2s cubic-bezier(.4,0,.7,.4);}
|
/* CLOSE / drag-back: reveal the list underneath (parallax in) while the conversation slides off right
|
||||||
|
(.content moved by an inline transform in showWelcome()/the edge-drag gesture). */
|
||||||
|
body.chat-closing .content, body.chat-dragging .content{display:block!important;z-index:2;box-shadow:-12px 0 30px rgba(15,23,42,.18);will-change:transform;}
|
||||||
|
body.chat-closing .chatcol{display:flex!important;position:absolute;inset:0;z-index:0;animation:bzListParallaxIn .28s cubic-bezier(.32,.72,0,1);}
|
||||||
body.chat-dragging .chatcol{display:flex!important;position:absolute;inset:0;z-index:0;}
|
body.chat-dragging .chatcol{display:flex!important;position:absolute;inset:0;z-index:0;}
|
||||||
/* Bigger touch targets */
|
/* Bigger touch targets */
|
||||||
.chat-row{padding:.7rem .85rem;}
|
.chat-row{padding:.7rem .85rem;}
|
||||||
@@ -1117,7 +1120,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="/icons.js?v=6"></script>
|
<script src="/icons.js?v=6"></script>
|
||||||
<script>window.__BUILD='2026-07-18-batch112';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
<script>window.__BUILD='2026-07-18-batch113';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
|
||||||
@@ -2730,10 +2733,8 @@ async function openConvo(kind,id){
|
|||||||
convoIsGroup=(kind==='group');
|
convoIsGroup=(kind==='group');
|
||||||
const el=document.getElementById('chatPanel'); el.classList.remove('center');
|
const el=document.getElementById('chatPanel'); el.classList.remove('center');
|
||||||
el.innerHTML=convoShellHTML(it);
|
el.innerHTML=convoShellHTML(it);
|
||||||
// Smooth page transition: replay the slide-in every time a chat opens on mobile — even chat→chat where
|
// (The open slide/parallax is driven by the body.chat-opening class added in selectChat — animating the
|
||||||
// body.chat-open already exists (a CSS animation only auto-plays on a class change, so it looked instant
|
// whole .content pane over the list, not the inner panel, so it reads as a native push.)
|
||||||
// after the first open). Force a restart via reflow, then set the animation inline.
|
|
||||||
if(window.matchMedia('(max-width:760px)').matches){ el.style.animation='none'; void el.offsetWidth; el.style.animation='bzConvoIn .26s cubic-bezier(.2,.7,.3,1)'; }
|
|
||||||
// #1: drag-and-drop a file/video/image anywhere on the conversation to send it.
|
// #1: drag-and-drop a file/video/image anywhere on the conversation to send it.
|
||||||
el.ondragover=(e)=>{ if(e.dataTransfer&&Array.from(e.dataTransfer.types||[]).includes('Files')){ e.preventDefault(); el.classList.add('drag-over'); } };
|
el.ondragover=(e)=>{ if(e.dataTransfer&&Array.from(e.dataTransfer.types||[]).includes('Files')){ e.preventDefault(); el.classList.add('drag-over'); } };
|
||||||
el.ondragleave=(e)=>{ if(e.relatedTarget===null||!el.contains(e.relatedTarget)) el.classList.remove('drag-over'); };
|
el.ondragleave=(e)=>{ if(e.relatedTarget===null||!el.contains(e.relatedTarget)) el.classList.remove('drag-over'); };
|
||||||
@@ -2865,7 +2866,9 @@ async function openConvo(kind,id){
|
|||||||
const _pinNewest=()=>{ if(!(selected&&selected.kind===kind&&selected.id===id)) return; const b=document.getElementById('msgs'); if(!b||b.querySelector('.new-sep')) return; if((b.scrollHeight-b.scrollTop-b.clientHeight)<1200) b.scrollTop=b.scrollHeight; };
|
const _pinNewest=()=>{ if(!(selected&&selected.kind===kind&&selected.id===id)) return; const b=document.getElementById('msgs'); if(!b||b.querySelector('.new-sep')) return; if((b.scrollHeight-b.scrollTop-b.clientHeight)<1200) b.scrollTop=b.scrollHeight; };
|
||||||
[0,120,320,600,1000,1600,2400,3400].forEach((d)=>setTimeout(_pinNewest, d));
|
[0,120,320,600,1000,1600,2400,3400].forEach((d)=>setTimeout(_pinNewest, d));
|
||||||
const _pinBox=document.getElementById('msgs'); if(_pinBox){ _pinBox.querySelectorAll('img').forEach((im)=>{ if(!im.complete) im.addEventListener('load', _pinNewest, {once:true}); }); }
|
const _pinBox=document.getElementById('msgs'); if(_pinBox){ _pinBox.querySelectorAll('img').forEach((im)=>{ if(!im.complete) im.addEventListener('load', _pinNewest, {once:true}); }); }
|
||||||
setTimeout(()=>{ try{ const b=document.getElementById('msgs'); if(b && window.bzDbg) bzDbg('convoOpen',{count:THREAD.length, scrollTop:Math.round(b.scrollTop), scrollH:Math.round(b.scrollHeight), clientH:Math.round(b.clientHeight), atBottom:(b.scrollHeight-b.scrollTop-b.clientHeight)<8, newsep:!!b.querySelector('.new-sep')}); }catch(_){} }, 3600);
|
setTimeout(()=>{ try{ const b=document.getElementById('msgs'); if(b && window.bzDbg){ let tall={h:0,c:''}; b.querySelectorAll('.bubble,img,.att-img').forEach(el=>{ const h=el.getBoundingClientRect().height; if(h>tall.h) tall={h:Math.round(h), c:(el.className||el.tagName||'').toString().slice(0,36)}; }); bzDbg('convoOpen',{count:THREAD.length, scrollTop:Math.round(b.scrollTop), scrollH:Math.round(b.scrollHeight), clientH:Math.round(b.clientHeight), atBottom:(b.scrollHeight-b.scrollTop-b.clientHeight)<8, newsep:!!b.querySelector('.new-sep'), tallH:tall.h, tallC:tall.c}); } }catch(_){} }, 3600);
|
||||||
|
// Also watch for a transient scrollHeight balloon (the stretch/jump) and report what's tallest when it spikes.
|
||||||
|
if(window.bzDbg){ let _spk=0; const _watch=setInterval(()=>{ const b=document.getElementById('msgs'); if(!(selected&&selected.kind===kind&&selected.id===id)||!b){ clearInterval(_watch); return; } const per=b.scrollHeight/Math.max(1,THREAD.length); if(per>200 && _spk<2){ _spk++; let tall={h:0,c:''}; b.querySelectorAll('.bubble,img').forEach(el=>{ const h=el.getBoundingClientRect().height; if(h>tall.h) tall={h:Math.round(h), c:(el.className||el.tagName||'').toString().slice(0,36), src:(el.getAttribute&&el.getAttribute('src')||'').slice(-24)}; }); bzDbg('spike',{scrollH:Math.round(b.scrollHeight), per:Math.round(per), tallH:tall.h, tallC:tall.c, tallSrc:tall.src}); } }, 200); setTimeout(()=>clearInterval(_watch), 6000); }
|
||||||
}
|
}
|
||||||
// ---------- @mentions (group chat) ----------
|
// ---------- @mentions (group chat) ----------
|
||||||
let mentionItems=[], mentionIdx=0, mentionStart=-1;
|
let mentionItems=[], mentionIdx=0, mentionStart=-1;
|
||||||
@@ -3039,6 +3042,8 @@ async function selectChat(kind,id){
|
|||||||
if(_selMode) exitSelect(); // #1: leave forward-selection when changing chats
|
if(_selMode) exitSelect(); // #1: leave forward-selection when changing chats
|
||||||
selected={kind,id};
|
selected={kind,id};
|
||||||
document.body.classList.add('chat-open'); // mobile: show the conversation pane
|
document.body.classList.add('chat-open'); // mobile: show the conversation pane
|
||||||
|
// Smooth native-style push: reveal the list underneath with a parallax while the conversation slides in.
|
||||||
|
if(window.matchMedia('(max-width:760px)').matches){ document.body.classList.add('chat-opening'); clearTimeout(window.__chatOpenT); window.__chatOpenT=setTimeout(()=>document.body.classList.remove('chat-opening'), 340); }
|
||||||
const it=rowFor(kind,id); _openUnread=(it&&it.unread)||0; if(it) it.unread=0; // capture before reset (#3: open at first unread)
|
const it=rowFor(kind,id); _openUnread=(it&&it.unread)||0; if(it) it.unread=0; // capture before reset (#3: open at first unread)
|
||||||
renderChats(searchVal());
|
renderChats(searchVal());
|
||||||
updateRailUnread();
|
updateRailUnread();
|
||||||
|
|||||||
Reference in New Issue
Block a user