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",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/addon-webgl": "^0.19.0",
"@xterm/xterm": "^5.5.0",
"electron-store": "^8.1.0",
"framer-motion": "^10.16.16",
@ -5866,6 +5867,12 @@
"@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": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz",

View File

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

View File

@ -3,6 +3,7 @@ import { motion } from 'framer-motion';
import { Terminal as XTerm } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import { WebLinksAddon } from '@xterm/addon-web-links';
import { WebglAddon } from '@xterm/addon-webgl';
import '@xterm/xterm/css/xterm.css';
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 resizeObserverRef = useRef(null);
const contextMenuHandlerRef = useRef(null);
const resizeTimeoutRef = useRef(null);
const webglAddonRef = useRef(null);
const onConnectionChangeRef = useRef(onConnectionChange);
onConnectionChangeRef.current = onConnectionChange;
@ -154,8 +157,12 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
cursorStyle: 'bar',
fontSize: 14,
fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, monospace",
lineHeight: 1.5,
scrollback: 2000,
lineHeight: 1.4,
scrollback: 1000, // 减少滚动缓冲区提升性能
fastScrollModifier: 'alt', // 快速滚动
fastScrollSensitivity: 5,
smoothScrollDuration: 0, // 禁用平滑滚动提升性能
scrollSensitivity: 1,
theme: {
// 赛博朋克主题配色
background: '#050810',
@ -195,6 +202,19 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
xtermRef.current = term;
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(() => {
fitAddon.fit();
}, 0);
@ -229,8 +249,14 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
};
container.addEventListener('contextmenu', contextMenuHandlerRef.current);
// 使用防抖的 ResizeObserver 避免频繁调用
resizeObserverRef.current = new ResizeObserver(() => {
fitTerminal();
if (resizeTimeoutRef.current) {
clearTimeout(resizeTimeoutRef.current);
}
resizeTimeoutRef.current = setTimeout(() => {
fitTerminal();
}, 100); // 100ms 防抖
});
resizeObserverRef.current.observe(container);
@ -268,6 +294,10 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
clearTimeout(initTimerRef.current);
}
if (resizeTimeoutRef.current) {
clearTimeout(resizeTimeoutRef.current);
}
if (resizeObserverRef.current) {
resizeObserverRef.current.disconnect();
resizeObserverRef.current = null;
@ -289,6 +319,12 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
connectionIdRef.current = null;
}
// 清理 WebGL 渲染器
if (webglAddonRef.current) {
webglAddonRef.current.dispose();
webglAddonRef.current = null;
}
if (xtermRef.current) {
xtermRef.current.dispose();
xtermRef.current = null;
@ -329,15 +365,13 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
setTimeout(() => connect(), 100);
}, [connect]);
// 工具栏按钮组件
// 工具栏按钮组件 - 简化动画提升性能
const ToolButton = ({ onClick, disabled, active, title, children }) => (
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
<button
onClick={onClick}
disabled={disabled}
className={`
p-2 rounded-lg transition-all duration-200
p-2 rounded-lg transition-colors duration-100
${active
? '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'
@ -347,15 +381,13 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
title={title}
>
{children}
</motion.button>
</button>
);
return (
<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 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="absolute inset-0 cyber-grid opacity-10 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">
@ -383,10 +415,8 @@ function Terminal({ tabId, hostId, onConnectionChange, onShowCommandPalette, onT
</div>
) : connectionId ? (
<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"
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)' }}
/>
<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">
{/* 命令提示 */}
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
<button
onClick={onShowCommandPalette}
className="flex items-center gap-2 px-3 py-1.5 rounded-lg btn-cyber text-sm text-shell-accent"
title="命令面板 (Ctrl+K)"
>
<FiCommand size={14} />
<span className="hidden sm:inline font-display tracking-wide">COMMANDS</span>
</motion.button>
</button>
<div className="divider-vertical h-6 mx-1" />