iOS: smoother animation when toggling fullscreen editor

Hunter
Friday, December 19th, 2025 at 5:21 PM

index.html+48-15

index.html
 53  			width: 50%;
 54  			height: 100%;
 55  			position: relative;
 56 +			transition: opacity 0.1s ease-out; /* Added transition for smoother opacity hack */
 57  		}
 58  
 59  		.preview-pane {

610  			// Initial render
611  			updatePreview();
612  
    -			// Track editor focus for mobile keyboard detection
613 +			// Track editor focus and handle keyboard dismissal
614  			editorView.contentDOM.addEventListener('focus', () => { isEditorFocused = true; });
    -			editorView.contentDOM.addEventListener('blur', () => { isEditorFocused = false; });
615 +			
616 +			editorView.contentDOM.addEventListener('blur', () => { 
617 +				isEditorFocused = false; 
618 +
619 +				// If we are on mobile and the keyboard mode is active, 
620 +				// exit immediately. Do not wait for visualViewport resize.
621 +				if (isMobileDevice() && document.body.classList.contains('mobile-keyboard-open')) {
622 +					
623 +					// 1. Hide editor immediately to prevent the "stutter/jump" visual
624 +					editorPane.style.opacity = '0';
625 +					
626 +					// 2. Remove the class to trigger layout engine (100% -> 50% height)
627 +					document.body.classList.remove('mobile-keyboard-open');
628 +					
629 +					// 3. Wait for layout to settle, then restore scroll and opacity
630 +					requestAnimationFrame(() => requestAnimationFrame(() => {
631 +						if (editorView) {
632 +							// Scroll cursor into view in the new 50% layout
633 +							const pos = editorView.state.selection.main.head;
634 +							const lineBlock = editorView.lineBlockAt(pos);
635 +							const targetScroll = lineBlock.top - (editorView.dom.clientHeight / 2);
636 +							editorView.scrollDOM.scrollTop = Math.max(0, targetScroll);
637 +						}
638 +						// Reveal the editor
639 +						editorPane.style.opacity = '';
640 +					}));
641 +				}
642 +			});
643  
644  			// Focus the editor
645  			editorView.focus();

681  			if (isKeyboardOpen && isEditorFocused) {
682  				document.body.classList.add('mobile-keyboard-open');
683  			} else if (!isKeyboardOpen) {
684 +				// Check if the class is still there (it might have been removed by blur already)
685  				const wasKeyboardOpen = document.body.classList.contains('mobile-keyboard-open');
686  
    -				if (wasKeyboardOpen && editorView) {
    -					// Hide editor, update layout, then scroll cursor into view
    -					const pos = editorView.state.selection.main.head;
    -					editorPane.style.opacity = '0';
    -					document.body.classList.remove('mobile-keyboard-open');
    -					requestAnimationFrame(() => requestAnimationFrame(() => {
    -						const lineBlock = editorView.lineBlockAt(pos);
    -						const targetScroll = lineBlock.top - (editorView.dom.clientHeight / 2);
    -						editorView.scrollDOM.scrollTop = Math.max(0, targetScroll);
    -						editorPane.style.opacity = '';
    -					}));
    -				} else {
    -					document.body.classList.remove('mobile-keyboard-open');
687 +				if (wasKeyboardOpen) {
688 +					// This is a fallback in case blur didn't catch it 
689 +					// (e.g. if the keyboard was dismissed via a gesture that didn't blur immediately)
690 +					if (editorView) {
691 +						const pos = editorView.state.selection.main.head;
692 +						editorPane.style.opacity = '0';
693 +						document.body.classList.remove('mobile-keyboard-open');
694 +						
695 +						requestAnimationFrame(() => requestAnimationFrame(() => {
696 +							const lineBlock = editorView.lineBlockAt(pos);
697 +							const targetScroll = lineBlock.top - (editorView.dom.clientHeight / 2);
698 +							editorView.scrollDOM.scrollTop = Math.max(0, targetScroll);
699 +							editorPane.style.opacity = '';
700 +						}));
701 +					} else {
702 +						document.body.classList.remove('mobile-keyboard-open');
703 +					}
704  				}
705  			}
706  		}