// Mobile keyboard handling let initialViewportHeight = window.innerHeight; function adjustForKeyboard() { const currentHeight = window.visualViewport ? window.visualViewport.height : window.innerHeight; const diff = initialViewportHeight - currentHeight; const messagesWrapper = document.getElementById('messagesWrapper'); if (diff > 150) { // Keyboard is likely open keyboardOpen = true; if (messagesWrapper) { messagesWrapper.style.paddingBottom = '140px'; // Even more padding for mobile keyboard // Reset scroll state and aggressively scroll to bottom userScrolled = false; // Multiple scroll attempts with increasing delays setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150; }, 100); setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150; }, 250); setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150; }, 400); // iOS often needs even more attempts if (isIOS) { setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200; }, 600); setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200; }, 800); setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200; }, 1000); } } } else { keyboardOpen = false; if (messagesWrapper) { messagesWrapper.style.paddingBottom = '80px'; // Scroll to bottom when keyboard closes too setTimeout(() => { if (!userScrolled) { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100; } }, 200); setTimeout(() => { if (!userScrolled) { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100; } }, 400); } } } // Listen for viewport changes (keyboard open/close) if (window.visualViewport) { window.visualViewport.addEventListener('resize', adjustForKeyboard); } else { window.addEventListener('resize', adjustForKeyboard); } // Mobile-optimized focus handling for textarea document.addEventListener('DOMContentLoaded', function() { const messageInput = document.getElementById('messageInput'); if (messageInput) { messageInput.addEventListener('focus', function() { // Reset scroll state when focusing input userScrolled = false; // Multiple timeout attempts for different mobile browsers setTimeout(adjustForKeyboard, 200); setTimeout(adjustForKeyboard, 400); if (isMobile) { setTimeout(adjustForKeyboard, 600); setTimeout(() => { const messagesWrapper = document.getElementById('messagesWrapper'); if (messagesWrapper) { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200; } }, 800); // Additional scroll attempt for stubborn mobile browsers setTimeout(() => { const messagesWrapper = document.getElementById('messagesWrapper'); if (messagesWrapper) { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250; } }, 1200); } }); messageInput.addEventListener('blur', function() { setTimeout(adjustForKeyboard, 200); setTimeout(adjustForKeyboard, 400); }); // Handle input events for better mobile experience messageInput.addEventListener('input', function() { if (isMobile && !userScrolled) { setTimeout(() => { scrollToBottom(false); }, 100); } }); } }); // Mobile-optimized auto-scroll functionality let isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth <= 768; let isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent); let keyboardOpen = false; function scrollToBottom(smooth = true) { const messagesWrapper = document.getElementById('messagesWrapper'); if (messagesWrapper) { // On mobile, especially iOS, direct scrollTop assignment is more reliable if (isMobile) { // Different scroll behavior based on keyboard state const extraPadding = keyboardOpen ? 200 : 100; const targetScroll = messagesWrapper.scrollHeight + extraPadding; messagesWrapper.scrollTop = targetScroll; // Multiple aggressive scroll attempts for mobile reliability setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + extraPadding; }, 50); setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + extraPadding; }, 150); setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + extraPadding; }, 300); // Extra attempts when just viewing (no keyboard) if (!keyboardOpen) { setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150; }, 500); setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150; }, 750); } // Final iOS-specific attempt if (isIOS) { const finalPadding = keyboardOpen ? 250 : 200; setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + finalPadding; }, keyboardOpen ? 500 : 1000); } } else { if (smooth) { messagesWrapper.scrollTo({ top: messagesWrapper.scrollHeight, behavior: 'smooth' }); } else { messagesWrapper.scrollTop = messagesWrapper.scrollHeight; } } } } // Function to check if user is near bottom of messages function isNearBottom() { const messagesWrapper = document.getElementById('messagesWrapper'); if (!messagesWrapper) return true; // Larger threshold for mobile to account for touch scrolling momentum const threshold = isMobile ? 150 : 100; return messagesWrapper.scrollHeight - messagesWrapper.scrollTop - messagesWrapper.clientHeight < threshold; } // Mobile scroll state management let lastScrollTop = 0; let userScrolled = false; let scrollTimeout; let touchStartY = 0; let isScrolling = false; // Detect user scroll with mobile optimizations document.addEventListener('DOMContentLoaded', function() { const messagesWrapper = document.getElementById('messagesWrapper'); if (messagesWrapper) { // Handle touch events for better mobile detection messagesWrapper.addEventListener('touchstart', function(e) { touchStartY = e.touches[0].clientY; isScrolling = false; clearTimeout(scrollTimeout); }, { passive: true }); messagesWrapper.addEventListener('touchmove', function(e) { if (!isScrolling) { isScrolling = true; const touchY = e.touches[0].clientY; const deltaY = touchStartY - touchY; // If user is swiping up (scrolling up), mark as user scrolled if (deltaY < -10 && !isNearBottom()) { userScrolled = true; } } }, { passive: true }); messagesWrapper.addEventListener('touchend', function() { // Check scroll position after touch ends with delay for momentum scrolling setTimeout(() => { if (isNearBottom()) { userScrolled = false; } isScrolling = false; }, 300); }, { passive: true }); // Regular scroll event with debouncing for mobile messagesWrapper.addEventListener('scroll', function() { clearTimeout(scrollTimeout); scrollTimeout = setTimeout(() => { const currentScrollTop = messagesWrapper.scrollTop; const maxScroll = messagesWrapper.scrollHeight - messagesWrapper.clientHeight; // More lenient detection for mobile const upScrollThreshold = isMobile ? 30 : 50; // If user scrolled up manually, don't auto-scroll for new messages if (currentScrollTop < lastScrollTop && currentScrollTop < maxScroll - upScrollThreshold) { userScrolled = true; } // If user scrolled to near bottom, resume auto-scrolling if (isNearBottom()) { userScrolled = false; } lastScrollTop = currentScrollTop; }, isMobile ? 150 : 50); // Longer debounce for mobile }, { passive: true }); } }); // Mobile-optimized message adding function function addMessageWithAutoScroll(messageElement) { const messagesContainer = document.getElementById('messagesContainer'); if (messagesContainer && messageElement) { messagesContainer.appendChild(messageElement); // Only auto-scroll if user hasn't manually scrolled up if (!userScrolled) { const messagesWrapper = document.getElementById('messagesWrapper'); if (messagesWrapper) { // Immediate first scroll messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100; // Progressive scroll attempts with different padding setTimeout(() => { const padding = keyboardOpen ? 200 : 150; messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding; }, 100); setTimeout(() => { const padding = keyboardOpen ? 200 : 150; messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding; }, 250); // Extra attempts when just viewing (no keyboard) if (!keyboardOpen && isMobile) { setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200; }, 400); setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250; }, 600); // Final attempt for stubborn browsers setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 300; }, 1000); } // iOS-specific final attempt if (isIOS) { const finalPadding = keyboardOpen ? 250 : 300; setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + finalPadding; }, keyboardOpen ? 600 : 1200); } } } } } // Force scroll with mobile optimizations function forceScrollToBottom() { userScrolled = false; const messagesWrapper = document.getElementById('messagesWrapper'); if (messagesWrapper) { // Immediate aggressive scroll messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150; setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 150; }, 100); setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 200; }, 300); // Extra attempts for iOS if (isIOS) { setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250; }, 500); setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250; }, 700); } } } // Mobile-optimized mutation observer document.addEventListener('DOMContentLoaded', function() { const messagesContainer = document.getElementById('messagesContainer'); if (messagesContainer) { // Create a MutationObserver to watch for new messages const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { // Check if any added nodes are message elements const hasNewMessage = Array.from(mutation.addedNodes).some(node => node.nodeType === Node.ELEMENT_NODE && (node.classList.contains('message-group') || node.querySelector('.message-group')) ); if (hasNewMessage && !userScrolled) { const messagesWrapper = document.getElementById('messagesWrapper'); if (messagesWrapper) { // Immediate scroll messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 100; setTimeout(() => { const padding = keyboardOpen ? 200 : 150; messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding; }, 100); setTimeout(() => { const padding = keyboardOpen ? 250 : 200; messagesWrapper.scrollTop = messagesWrapper.scrollHeight + padding; }, 300); // Extra scrolling when just viewing (no keyboard) if (!keyboardOpen && isMobile) { setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 250; }, 500); setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 300; }, 800); // Final aggressive attempt for viewing mode setTimeout(() => { messagesWrapper.scrollTop = messagesWrapper.scrollHeight + 350; }, 1200); } } } } }); }); // Start observing observer.observe(messagesContainer, { childList: true, subtree: true }); } }); // Expose functions globally so your existing JavaScript can use them window.ByteChat = window.ByteChat || {}; window.ByteChat.scrollToBottom = scrollToBottom; window.ByteChat.forceScrollToBottom = forceScrollToBottom; window.ByteChat.addMessageWithAutoScroll = addMessageWithAutoScroll;