Fix scroll-to-bottom in builder page
- Simplified scrollChatToBottom() with more reliable techniques - Added scrollIntoView on last message as fallback - Added additional scroll with direct scrollTop assignment after delay - Added CSS optimizations for scrolling behavior (-webkit-overflow-scrolling, transform, will-change) - Added more robust scroll attempts in renderMessages after DOM updates
This commit is contained in:
@@ -55,6 +55,10 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
scroll-behavior: auto;
|
||||||
|
transform: translateZ(0);
|
||||||
|
will-change: scroll-position;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-shell.builder-single>* {
|
.app-shell.builder-single>* {
|
||||||
|
|||||||
@@ -1520,7 +1520,7 @@ function closeHistoryModal() {
|
|||||||
if (el.historyModal) el.historyModal.style.display = 'none';
|
if (el.historyModal) el.historyModal.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollChatToBottom(force = false) {
|
function scrollChatToBottom() {
|
||||||
if (!el.chatArea) return;
|
if (!el.chatArea) return;
|
||||||
const target = el.chatArea;
|
const target = el.chatArea;
|
||||||
|
|
||||||
@@ -1529,91 +1529,48 @@ function scrollChatToBottom(force = false) {
|
|||||||
target.scrollTop = target.scrollHeight;
|
target.scrollTop = target.scrollHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use multiple techniques to ensure scroll happens after content is rendered
|
// Use multiple scroll techniques in sequence for reliability
|
||||||
// 1. Immediate scroll attempt
|
// 1. Immediate scroll
|
||||||
doScroll();
|
doScroll();
|
||||||
|
|
||||||
// 2. After short delays to allow DOM updates
|
// 2. After DOM updates
|
||||||
setTimeout(() => { doScroll(); }, 10);
|
setTimeout(doScroll, 50);
|
||||||
setTimeout(() => { doScroll(); }, 50);
|
setTimeout(doScroll, 100);
|
||||||
setTimeout(() => { doScroll(); }, 100);
|
setTimeout(doScroll, 200);
|
||||||
setTimeout(() => { doScroll(); }, 250);
|
setTimeout(doScroll, 500);
|
||||||
setTimeout(() => { doScroll(); }, 500);
|
|
||||||
|
|
||||||
// 3. Longer delay for content to fully settle (especially on page load)
|
// 3. After content fully renders
|
||||||
setTimeout(() => { doScroll(); }, 1000);
|
setTimeout(doScroll, 1000);
|
||||||
setTimeout(() => { doScroll(); }, 2000);
|
|
||||||
|
|
||||||
// 4. Use requestAnimationFrame for smooth scrolling
|
// 4. requestAnimationFrame for smooth scrolling
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
doScroll();
|
doScroll();
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(doScroll);
|
||||||
doScroll();
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
doScroll();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 5. Wait for ALL images to load before scrolling
|
// 5. Scroll to last message element if it exists (most reliable method)
|
||||||
const images = target.querySelectorAll('img');
|
setTimeout(() => {
|
||||||
let imagesToLoad = 0;
|
const lastMessage = target.querySelector('.message:last-child');
|
||||||
|
if (lastMessage) {
|
||||||
images.forEach((img) => {
|
lastMessage.scrollIntoView({ behavior: 'smooth', block: 'end' });
|
||||||
if (!img.complete) {
|
} else {
|
||||||
imagesToLoad++;
|
// Fallback to regular scroll
|
||||||
img.addEventListener('load', () => {
|
doScroll();
|
||||||
imagesToLoad--;
|
|
||||||
doScroll();
|
|
||||||
// Scroll again after a short delay to ensure layout is updated
|
|
||||||
setTimeout(() => doScroll(), 100);
|
|
||||||
}, { once: true });
|
|
||||||
|
|
||||||
img.addEventListener('error', () => {
|
|
||||||
imagesToLoad--;
|
|
||||||
doScroll();
|
|
||||||
}, { once: true });
|
|
||||||
}
|
}
|
||||||
});
|
}, 100);
|
||||||
|
|
||||||
// If there are images loading, scroll again after they all complete
|
// 6. ResizeObserver for dynamic content
|
||||||
if (imagesToLoad > 0) {
|
|
||||||
const checkImagesLoaded = setInterval(() => {
|
|
||||||
if (imagesToLoad === 0) {
|
|
||||||
clearInterval(checkImagesLoaded);
|
|
||||||
doScroll();
|
|
||||||
setTimeout(() => doScroll(), 100);
|
|
||||||
setTimeout(() => doScroll(), 500);
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
// Stop checking after 5 seconds to avoid infinite interval
|
|
||||||
setTimeout(() => clearInterval(checkImagesLoaded), 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. Use ResizeObserver to detect when content height changes
|
|
||||||
if (typeof ResizeObserver !== 'undefined') {
|
if (typeof ResizeObserver !== 'undefined') {
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
const resizeObserver = new ResizeObserver(() => doScroll());
|
||||||
for (const entry of entries) {
|
|
||||||
if (entry.target === target) {
|
|
||||||
doScroll();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
resizeObserver.observe(target);
|
resizeObserver.observe(target);
|
||||||
|
setTimeout(() => resizeObserver.disconnect(), 2000);
|
||||||
// Stop observing after a while
|
|
||||||
setTimeout(() => resizeObserver.disconnect(), 3000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Also scroll when any content finishes rendering (using MutationObserver)
|
// 7. MutationObserver for DOM changes
|
||||||
if (typeof MutationObserver !== 'undefined') {
|
if (typeof MutationObserver !== 'undefined') {
|
||||||
const observer = new MutationObserver(() => {
|
const observer = new MutationObserver(() => doScroll());
|
||||||
doScroll();
|
|
||||||
});
|
|
||||||
observer.observe(target, { childList: true, subtree: true });
|
observer.observe(target, { childList: true, subtree: true });
|
||||||
// Stop observing after a while to avoid memory leaks
|
setTimeout(() => observer.disconnect(), 2000);
|
||||||
setTimeout(() => observer.disconnect(), 3000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2027,8 +1984,23 @@ function renderMessages(session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scroll to bottom after DOM updates
|
||||||
scrollChatToBottom();
|
scrollChatToBottom();
|
||||||
|
|
||||||
|
// Additional scroll with longer delay to ensure all content is rendered
|
||||||
|
setTimeout(() => {
|
||||||
|
scrollChatToBottom();
|
||||||
|
// Force scroll by accessing scrollHeight directly
|
||||||
|
if (el.chatArea) {
|
||||||
|
el.chatArea.scrollTop = el.chatArea.scrollHeight;
|
||||||
|
// Scroll last element into view as fallback
|
||||||
|
const lastMessage = el.chatArea.querySelector('.message:last-child');
|
||||||
|
if (lastMessage) {
|
||||||
|
lastMessage.scrollIntoView({ block: 'end' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
|
||||||
updateExportButtonVisibility(session);
|
updateExportButtonVisibility(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user