Skip to content

Commit afe4180

Browse files
committed
Update animation and qrcode
1 parent 3b5725d commit afe4180

3 files changed

Lines changed: 197 additions & 21 deletions

File tree

-6.1 KB
Loading

src/static/js/animate.js

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
class TerminalBg {
2+
constructor (options = {}) {
3+
this.className = options.className || 'animate'
4+
this.color = options.color || '#666666'
5+
this.bgColor = options.bgColor || '#ffffff'
6+
this.fontSize = options.fontSize || 18
7+
this.speed = options.speed || 0.8
8+
this.lines = []
9+
this.canvas = null
10+
this.ctx = null
11+
this.animationId = null
12+
this.lastTime = 0
13+
this.frameInterval = 40
14+
this.init()
15+
}
16+
17+
getTexts () {
18+
return [
19+
'终端/SSH/SFTP/Telnet/串口/RDP/VNC客户端',
20+
'全局热键切换窗口可见性',
21+
'多平台支持 Linux/Mac/Windows',
22+
'多语言支持',
23+
'双击直接编辑远程文件',
24+
'公钥+密码认证',
25+
'支持 Zmodem (rz/sz)',
26+
'支持 SSH 隧道',
27+
'支持 Trzsz (trz/tsz)',
28+
'透明窗口',
29+
'终端背景图片',
30+
'全局/会话代理',
31+
'快捷命令',
32+
'UI/终端主题',
33+
'同步书签到 GitHub/Gitee',
34+
'AI 助手集成',
35+
'支持 DeepSeek/OpenAI',
36+
'深度链接支持',
37+
'命令行使用',
38+
'SSH/SFTP 文件传输',
39+
'批量操作',
40+
'多终端同步输入',
41+
'VNC 远程桌面',
42+
'RDP 远程桌面',
43+
'串口终端',
44+
'Telnet 连接',
45+
'FTP 客户端',
46+
'Terminal/SSH/SFTP/Telnet/Serial/RDP/VNC client',
47+
'Global hotkey to toggle window',
48+
'Multi platform: Linux/Mac/Windows',
49+
'Multi-language support',
50+
'Double click to edit remote files',
51+
'Auth with publicKey + password',
52+
'Support Zmodem (rz/sz)',
53+
'Support SSH tunnel',
54+
'Support Trzsz (trz/tsz)',
55+
'Transparent window',
56+
'Terminal background image',
57+
'Global/session proxy',
58+
'Quick commands',
59+
'UI/terminal themes',
60+
'Sync to GitHub/Gitee gist',
61+
'AI assistant integration',
62+
'Support DeepSeek/OpenAI',
63+
'Deep link support',
64+
'Command line usage',
65+
'SSH/SFTP file transfer',
66+
'Batch operations',
67+
'Multi-terminal sync input',
68+
'VNC remote desktop',
69+
'RDP remote desktop',
70+
'Serial port terminal',
71+
'Telnet connection',
72+
'FTP client'
73+
]
74+
}
75+
76+
init () {
77+
let container = document.querySelector(`.${this.className}`)
78+
if (!container) {
79+
container = document.createElement('div')
80+
container.className = this.className
81+
document.body.insertBefore(container, document.body.firstChild)
82+
}
83+
container.style.cssText = 'position:fixed;left:0;right:0;top:0;bottom:0;z-index:-1;overflow:hidden;'
84+
this.container = container
85+
this.canvas = document.createElement('canvas')
86+
this.canvas.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;'
87+
container.appendChild(this.canvas)
88+
this.ctx = this.canvas.getContext('2d')
89+
this.texts = this.getTexts()
90+
this.resize()
91+
window.addEventListener('resize', this.handleResize.bind(this))
92+
this.animate()
93+
}
94+
95+
handleResize () {
96+
if (this.resizeTimeout) {
97+
clearTimeout(this.resizeTimeout)
98+
}
99+
this.resizeTimeout = setTimeout(() => {
100+
this.resize()
101+
}, 100)
102+
}
103+
104+
resize () {
105+
const container = this.canvas.parentElement
106+
const dpr = Math.min(window.devicePixelRatio || 1, 2)
107+
const rect = container.getBoundingClientRect()
108+
this.canvas.width = rect.width * dpr
109+
this.canvas.height = rect.height * dpr
110+
this.canvas.style.width = rect.width + 'px'
111+
this.canvas.style.height = rect.height + 'px'
112+
this.ctx.scale(dpr, dpr)
113+
this.width = rect.width
114+
this.height = rect.height
115+
this.initLines()
116+
}
117+
118+
initLines () {
119+
const lineCount = Math.max(20, Math.floor(this.width / 80))
120+
const texts = this.texts
121+
this.lines = []
122+
const angle = Math.PI / 6
123+
for (let i = 0; i < lineCount; i++) {
124+
const progress = i / lineCount
125+
const startX = -this.width * 0.5 + progress * (this.width * 1.5)
126+
const startY = -this.height * 0.3 + progress * (this.height * 1.3) - Math.random() * 150
127+
this.lines.push({
128+
x: startX,
129+
y: startY,
130+
angle,
131+
speed: (0.3 + Math.random() * 0.4) * this.speed,
132+
text: texts[Math.floor(Math.random() * texts.length)],
133+
opacity: 0.08 + Math.random() * 0.1,
134+
fontSize: this.fontSize + Math.floor(Math.random() * 6)
135+
})
136+
}
137+
}
138+
139+
animate (timestamp = 0) {
140+
this.animationId = requestAnimationFrame(this.animate.bind(this))
141+
const delta = timestamp - this.lastTime
142+
if (delta < this.frameInterval) return
143+
this.lastTime = timestamp - (delta % this.frameInterval)
144+
this.ctx.fillStyle = this.bgColor
145+
this.ctx.fillRect(0, 0, this.width, this.height)
146+
for (const line of this.lines) {
147+
const moveX = Math.cos(line.angle) * line.speed * 2
148+
const moveY = Math.sin(line.angle) * line.speed * 2
149+
line.x += moveX
150+
line.y += moveY
151+
if (line.y > this.height + 50 || line.x > this.width + 50) {
152+
line.x = -this.width * 0.5 + Math.random() * this.width * 0.3
153+
line.y = -100 - Math.random() * 100
154+
line.text = this.texts[Math.floor(Math.random() * this.texts.length)]
155+
line.opacity = 0.08 + Math.random() * 0.1
156+
}
157+
this.ctx.save()
158+
this.ctx.translate(line.x, line.y)
159+
this.ctx.rotate(line.angle)
160+
this.ctx.font = `${line.fontSize}px monospace`
161+
this.ctx.fillStyle = this.hexToRgba(this.color, line.opacity)
162+
this.ctx.fillText(line.text, 0, 0)
163+
this.ctx.restore()
164+
}
165+
}
166+
167+
hexToRgba (hex, alpha) {
168+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
169+
if (result) {
170+
return `rgba(${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}, ${alpha})`
171+
}
172+
const rgbMatch = hex.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/)
173+
if (rgbMatch) {
174+
return `rgba(${rgbMatch[1]}, ${rgbMatch[2]}, ${rgbMatch[3]}, ${alpha})`
175+
}
176+
return `rgba(102, 102, 102, ${alpha})`
177+
}
178+
179+
destroy () {
180+
if (this.animationId) {
181+
cancelAnimationFrame(this.animationId)
182+
}
183+
window.removeEventListener('resize', this.handleResize.bind(this))
184+
if (this.canvas && this.canvas.parentElement) {
185+
this.canvas.parentElement.removeChild(this.canvas)
186+
}
187+
if (this.container && this.container.parentElement) {
188+
this.container.parentElement.removeChild(this.container)
189+
}
190+
}
191+
}
192+
193+
export default TerminalBg

src/views/parts/react-footer.pug

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,11 @@
11
//- script(crossorigin, src='//unpkg.zhimg.com/react@18/umd/react.production.min.js')
22
//- script(crossorigin, src='//unpkg.zhimg.com/react-dom@18/umd/react-dom.production.min.js')
3-
script(async, src='https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js')
4-
script(type='importmap').
5-
{
6-
"imports": {
7-
"three": "https://unpkg.com/three@0.155.0/build/three.module.js",
8-
"universe-bg": "//unpkg.com/universe-bg/dist/universe-bg.mjs"
9-
}
10-
}
11-
//- script(crossorigin, type='module', src='//unpkg.com/three@0.155.0/build/three.module.js')
12-
//- script(crossorigin, type='module', src='//unpkg.com/universe-bg/dist/universe-bg.mjs')
133
script(type='module').
14-
import UniverseBg from 'universe-bg'
15-
window.x = new UniverseBg({
4+
import TerminalBg from '/src/static/js/animate.js'
5+
window.x = new TerminalBg({
166
className: 'animate',
17-
// shootingStarCount: 150,
18-
// starCount: 1000,
19-
// starSize: 30,
20-
shootingStarSize: 0.4,
21-
shootingStarColor: 0x666666,
22-
starColor: 0x666666,
23-
bgColor: 0xffffff
24-
// starDistance: 80,
25-
// shootingStarDistance: 40
7+
color: '#666666',
8+
bgColor: '#ffffff'
269
})
2710

2811
- if (dev)

0 commit comments

Comments
 (0)