perf: optimize terminal performance with WebGL renderer and reduced animations

This commit is contained in:
Ethanfly 2025-12-31 15:39:16 +08:00
parent eeff7271a1
commit d7da96c0c5
3 changed files with 56 additions and 20 deletions

7
package-lock.json generated
View File

@ -16,6 +16,7 @@
"@capacitor/status-bar": "^5.0.6", "@capacitor/status-bar": "^5.0.6",
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0", "@xterm/addon-web-links": "^0.11.0",
"@xterm/addon-webgl": "^0.19.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"framer-motion": "^10.16.16", "framer-motion": "^10.16.16",
@ -5866,6 +5867,12 @@
"@xterm/xterm": "^5.0.0" "@xterm/xterm": "^5.0.0"
} }
}, },
"node_modules/@xterm/addon-webgl": {
"version": "0.19.0",
"resolved": "https://registry.npmmirror.com/@xterm/addon-webgl/-/addon-webgl-0.19.0.tgz",
"integrity": "sha512-b3fMOsyLVuCeNJWxolACEUED0vm7qC0cy4wRvf3oURSzDTYVQiGPhTnhWZwIHdvC48Y+oLhvYXnY4XDXPoJo6A==",
"license": "MIT"
},
"node_modules/@xterm/xterm": { "node_modules/@xterm/xterm": {
"version": "5.5.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",

View File

@ -76,6 +76,7 @@
"@capacitor/status-bar": "^5.0.6", "@capacitor/status-bar": "^5.0.6",
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0", "@xterm/addon-web-links": "^0.11.0",
"@xterm/addon-webgl": "^0.19.0",
"@xterm/xterm": "^5.5.0", "@xterm/xterm": "^5.5.0",
"electron-store": "^8.1.0", "electron-store": "^8.1.0",
"framer-motion": "^10.16.16", "framer-motion": "^10.16.16",

View File

@ -3,6 +3,7 @@ import { motion } from 'framer-motion';
import { Terminal as XTerm } from '@xterm/xterm'; import { Terminal as XTerm } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit'; import { FitAddon } from '@xterm/addon-fit';
import { WebLinksAddon } from '@xterm/addon-web-links'; import { WebLinksAddon } from '@xterm/addon-web-links';
import { WebglAddon } from '@xterm/addon-webgl';
import '@xterm/xterm/css/xterm.css'; import '@xterm/xterm/css/xterm.css';
import { FiCommand, FiRefreshCw, FiInfo, FiFolder, FiActivity, FiZap } from 'react-icons/fi'; import { FiCommand, FiRefreshCw, FiInfo, FiFolder, FiActivity, FiZap } from 'react-icons/fi';
@ -19,6 +20,8 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
const hasConnectedRef = useRef(false); const hasConnectedRef = useRef(false);
const resizeObserverRef = useRef(null); const resizeObserverRef = useRef(null);
const contextMenuHandlerRef = useRef(null); const contextMenuHandlerRef = useRef(null);
const resizeTimeoutRef = useRef(null);
const webglAddonRef = useRef(null);
const onConnectionChangeRef = useRef(onConnectionChange); const onConnectionChangeRef = useRef(onConnectionChange);
onConnectionChangeRef.current = onConnectionChange; onConnectionChangeRef.current = onConnectionChange;
@ -154,8 +157,12 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
cursorStyle: 'bar', cursorStyle: 'bar',
fontSize: 14, fontSize: 14,
fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, monospace", fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, monospace",
lineHeight: 1.5, lineHeight: 1.4,
scrollback: 2000, scrollback: 1000, // 减少滚动缓冲区提升性能
fastScrollModifier: 'alt', // 快速滚动
fastScrollSensitivity: 5,
smoothScrollDuration: 0, // 禁用平滑滚动提升性能
scrollSensitivity: 1,
theme: { theme: {
// 赛博朋克主题配色 // 赛博朋克主题配色
background: '#050810', background: '#050810',
@ -195,6 +202,19 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
xtermRef.current = term; xtermRef.current = term;
fitAddonRef.current = fitAddon; fitAddonRef.current = fitAddon;
// 尝试加载 WebGL 渲染器提升性能
try {
const webglAddon = new WebglAddon();
webglAddon.onContextLoss(() => {
webglAddon.dispose();
webglAddonRef.current = null;
});
term.loadAddon(webglAddon);
webglAddonRef.current = webglAddon;
} catch (e) {
console.warn('WebGL 渲染器不可用,使用默认渲染器:', e);
}
setTimeout(() => { setTimeout(() => {
fitAddon.fit(); fitAddon.fit();
}, 0); }, 0);
@ -229,8 +249,14 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
}; };
container.addEventListener('contextmenu', contextMenuHandlerRef.current); container.addEventListener('contextmenu', contextMenuHandlerRef.current);
// 使用防抖的 ResizeObserver 避免频繁调用
resizeObserverRef.current = new ResizeObserver(() => { resizeObserverRef.current = new ResizeObserver(() => {
if (resizeTimeoutRef.current) {
clearTimeout(resizeTimeoutRef.current);
}
resizeTimeoutRef.current = setTimeout(() => {
fitTerminal(); fitTerminal();
}, 100); // 100ms 防抖
}); });
resizeObserverRef.current.observe(container); resizeObserverRef.current.observe(container);
@ -268,6 +294,10 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
clearTimeout(initTimerRef.current); clearTimeout(initTimerRef.current);
} }
if (resizeTimeoutRef.current) {
clearTimeout(resizeTimeoutRef.current);
}
if (resizeObserverRef.current) { if (resizeObserverRef.current) {
resizeObserverRef.current.disconnect(); resizeObserverRef.current.disconnect();
resizeObserverRef.current = null; resizeObserverRef.current = null;
@ -289,6 +319,12 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
connectionIdRef.current = null; connectionIdRef.current = null;
} }
// 清理 WebGL 渲染器
if (webglAddonRef.current) {
webglAddonRef.current.dispose();
webglAddonRef.current = null;
}
if (xtermRef.current) { if (xtermRef.current) {
xtermRef.current.dispose(); xtermRef.current.dispose();
xtermRef.current = null; xtermRef.current = null;
@ -329,15 +365,13 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
setTimeout(() => connect(), 100); setTimeout(() => connect(), 100);
}, [connect]); }, [connect]);
// 工具栏按钮组件 // 工具栏按钮组件 - 简化动画提升性能
const ToolButton = ({ onClick, disabled, active, title, children }) => ( const ToolButton = ({ onClick, disabled, active, title, children }) => (
<motion.button <button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={onClick} onClick={onClick}
disabled={disabled} disabled={disabled}
className={` className={`
p-2 rounded-lg transition-all duration-200 p-2 rounded-lg transition-colors duration-100
${active ${active
? 'bg-shell-accent/20 text-shell-accent border border-shell-accent/40' ? 'bg-shell-accent/20 text-shell-accent border border-shell-accent/40'
: 'bg-shell-card/50 border border-shell-border text-shell-text-dim hover:text-shell-text hover:border-shell-accent/30 hover:bg-shell-accent/10' : 'bg-shell-card/50 border border-shell-border text-shell-text-dim hover:text-shell-text hover:border-shell-accent/30 hover:bg-shell-accent/10'
@ -347,15 +381,13 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
title={title} title={title}
> >
{children} {children}
</motion.button> </button>
); );
return ( return (
<div ref={containerRef} className="h-full flex flex-col bg-shell-bg relative overflow-hidden"> <div ref={containerRef} className="h-full flex flex-col bg-shell-bg relative overflow-hidden">
{/* 背景装饰 */} {/* 简化背景装饰以提升性能 */}
<div className="absolute inset-0 cyber-grid opacity-20 pointer-events-none" /> <div className="absolute inset-0 cyber-grid opacity-10 pointer-events-none" />
<div className="absolute top-0 right-0 w-64 h-64 bg-shell-accent/5 rounded-full blur-3xl pointer-events-none" />
<div className="absolute bottom-0 left-0 w-48 h-48 bg-shell-neon-purple/5 rounded-full blur-3xl pointer-events-none" />
{/* 终端工具栏 */} {/* 终端工具栏 */}
<div className="h-12 bg-shell-surface/80 backdrop-blur-xl border-b border-shell-border flex items-center px-4 justify-between flex-shrink-0 relative z-10"> <div className="h-12 bg-shell-surface/80 backdrop-blur-xl border-b border-shell-border flex items-center px-4 justify-between flex-shrink-0 relative z-10">
@ -383,10 +415,8 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
</div> </div>
) : connectionId ? ( ) : connectionId ? (
<div className="flex items-center gap-2 text-shell-success text-sm"> <div className="flex items-center gap-2 text-shell-success text-sm">
<motion.span <span
className="w-2 h-2 rounded-full bg-shell-success" className="w-2 h-2 rounded-full bg-shell-success"
animate={{ scale: [1, 1.2, 1], opacity: [0.7, 1, 0.7] }}
transition={{ duration: 2, repeat: Infinity }}
style={{ boxShadow: '0 0 8px rgba(0, 255, 136, 0.6)' }} style={{ boxShadow: '0 0 8px rgba(0, 255, 136, 0.6)' }}
/> />
<span className="font-display tracking-wide">CONNECTED</span> <span className="font-display tracking-wide">CONNECTED</span>
@ -403,16 +433,14 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
{/* 右侧工具按钮 */} {/* 右侧工具按钮 */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{/* 命令提示 */} {/* 命令提示 */}
<motion.button <button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
onClick={onShowCommandPalette} onClick={onShowCommandPalette}
className="flex items-center gap-2 px-3 py-1.5 rounded-lg btn-cyber text-sm text-shell-accent" className="flex items-center gap-2 px-3 py-1.5 rounded-lg btn-cyber text-sm text-shell-accent"
title="命令面板 (Ctrl+K)" title="命令面板 (Ctrl+K)"
> >
<FiCommand size={14} /> <FiCommand size={14} />
<span className="hidden sm:inline font-display tracking-wide">COMMANDS</span> <span className="hidden sm:inline font-display tracking-wide">COMMANDS</span>
</motion.button> </button>
<div className="divider-vertical h-6 mx-1" /> <div className="divider-vertical h-6 mx-1" />