From 61a72dee7c2259927f0f3593d285633e8ba9d86c Mon Sep 17 00:00:00 2001 From: kngin Date: Sun, 1 Mar 2026 23:03:04 +0800 Subject: [PATCH] fix(html): stabilize mobile keyboard viewport and background --- html/src/components/terminal/xterm/index.ts | 47 ++++++++++++++++++++- html/src/style/index.scss | 32 +++++++++++++- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/html/src/components/terminal/xterm/index.ts b/html/src/components/terminal/xterm/index.ts index b332154b2..d69a90096 100644 --- a/html/src/components/terminal/xterm/index.ts +++ b/html/src/components/terminal/xterm/index.ts @@ -101,6 +101,7 @@ export class Xterm { private reconnect = true; private doReconnect = true; private closeOnDisconnect = false; + private parent?: HTMLElement; private writeFunc = (data: ArrayBuffer) => this.writeData(new Uint8Array(data)); @@ -153,6 +154,7 @@ export class Xterm { @bind public open(parent: HTMLElement) { + this.parent = parent; this.terminal = new Terminal(this.options.termOptions); const { terminal, fitAddon, overlayAddon, clipboardAddon, webLinksAddon } = this; window.term = terminal as TtydTerminal; @@ -166,9 +168,33 @@ export class Xterm { terminal.loadAddon(webLinksAddon); terminal.open(parent); + this.syncPageBackground(); + this.syncViewport(); fitAddon.fit(); } + @bind + private syncPageBackground() { + const themeBackground = this.terminal?.options.theme?.background; + const color = typeof themeBackground === 'string' && themeBackground !== '' ? themeBackground : '#2b2b2b'; + document.documentElement.style.backgroundColor = color; + document.body.style.backgroundColor = color; + } + + @bind + private syncViewport() { + if (!this.parent) return; + const viewport = window.visualViewport; + if (viewport) { + const offsetTop = Math.max(0, Math.round(viewport.offsetTop)); + this.parent.style.height = `${Math.round(viewport.height)}px`; + this.parent.style.top = `${offsetTop}px`; + } else { + this.parent.style.height = ''; + this.parent.style.top = ''; + } + } + @bind private initListeners() { const { terminal, fitAddon, overlayAddon, register, sendData } = this; @@ -199,7 +225,25 @@ export class Xterm { this.overlayAddon?.showOverlay('\u2702', 200); }) ); - register(addEventListener(window, 'resize', () => fitAddon.fit())); + register( + addEventListener(window, 'resize', () => { + this.syncViewport(); + fitAddon.fit(); + }) + ); + if (window.visualViewport) { + register( + addEventListener(window.visualViewport, 'resize', () => { + this.syncViewport(); + fitAddon.fit(); + }) + ); + register( + addEventListener(window.visualViewport, 'scroll', () => { + this.syncViewport(); + }) + ); + } register(addEventListener(window, 'beforeunload', this.onWindowUnload)); } @@ -466,6 +510,7 @@ export class Xterm { break; } } + this.syncPageBackground(); } @bind diff --git a/html/src/style/index.scss b/html/src/style/index.scss index 0f9244b63..8543f43bf 100644 --- a/html/src/style/index.scss +++ b/html/src/style/index.scss @@ -4,15 +4,43 @@ body { min-height: 100%; margin: 0; overflow: hidden; + overscroll-behavior: none; + background-color: #2b2b2b; + position: fixed; + inset: 0; + width: 100%; } #terminal-container { - width: auto; + position: fixed; + top: 0; + left: 0; + right: 0; + width: 100%; height: 100%; - margin: 0 auto; + margin: 0; padding: 0; + overflow: hidden; .terminal { padding: 5px; height: calc(100% - 10px); } } + +.xterm .xterm-viewport { + overscroll-behavior-y: contain; + -webkit-overflow-scrolling: touch; + touch-action: pan-y; +} + +@media (hover: none) and (pointer: coarse) { + .xterm .xterm-viewport { + scrollbar-width: none; + } + + .xterm .xterm-viewport::-webkit-scrollbar { + width: 0; + height: 0; + display: none; + } +}