一些小东西

这里记录了我做过的一些小东西。还蛮有意思的


2025.12.06-2025.12.07

手势控制3D星云

一天,我同学在抖音上给我分享了一个视频:
一位网友用gemini 3,几下就做出了一个很有意思的demo,是利用摄像头实时拍摄,并识别手部动作,对星云粒子效果进行操控
我一开始想的是:啊这,我既没有梯子也没有钱,咋做啊。算了算了。

后来我灵机一动再加心血来潮,决定用deepseek帮我做。
在deepseek的帮助下我知道了,只需要在一个文件夹中做好 .html .css .js三个文件,再双击打开 .html,就好了,非常的简单。
于是我开始疯狂地和deepseek对话。

我的天呐,原来和AI对话来复制代码,真的不是一件容易的事情。因为AI很容易就把这个bug修完之后把另一个bug忘了。
我尝试了一般地提问、开新对话提问、先让他写提示词再提问…

不管怎么说,经过一个下午,我也是弄出来了一个demo
不过也就只有一个文件夹,所以只能在本地玩玩
是这个样子的

手势控制3D星云

另外我把代码也贴在这里(虽然全都是deepseek写的

这是html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>手势控制3D星云</title>
<link rel="stylesheet" href="style.css">
<!-- Three.js CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<!-- MediaPipe Hands CDN -->
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js"></script>
</head>
<body>
<div class="app-container">
<!-- 固定标题区域 -->
<header class="header">
<h1>
<span class="emoji">🌌</span>
<span class="title-text">手势控制3D星云</span>
</h1>
<p class="subtitle">通过摄像头捕捉手部动作,实时控制星云的形态变化</p>

<!-- 顶部手势图标 -->
<div class="top-gestures">
<div class="gesture-item">
<div class="gesture-icon">👆</div>
<div class="gesture-text">食指控制旋转</div>
</div>
<div class="gesture-item">
<div class="gesture-icon">🤏</div>
<div class="gesture-text">捏合控制大小</div>
</div>
<div class="gesture-item">
<div class="gesture-icon">🖐️</div>
<div class="gesture-text">手腕控制躁动</div>
</div>
</div>
</header>

<!-- 主要画布区域 -->
<main class="main-content">
<div class="canvas-container">
<div class="canvas-header">
<span class="canvas-icon">📹</span>
<span class="canvas-title">摄像头与手势检测</span>
</div>
<canvas id="video-canvas" class="canvas"></canvas>
<div class="canvas-footer" id="hand-status">等待启动...</div>
</div>

<div class="canvas-container">
<div class="canvas-header">
<span class="canvas-icon">🌠</span>
<span class="canvas-title">3D星云粒子系统</span>
</div>
<canvas id="particle-canvas" class="canvas"></canvas>
<div class="canvas-footer" id="particle-status">正在初始化...</div>
</div>
</main>

<!-- 控制按钮区域 -->
<div class="controls">
<button id="start-btn" class="btn btn-primary">启动摄像头</button>
<button id="stop-btn" class="btn btn-secondary">停止摄像头</button>
<button id="reset-btn" class="btn btn-secondary">重置星云</button>
</div>

<!-- 详细说明区域 -->
<div class="detailed-instructions">
<h2>手势控制详细说明</h2>
<div class="instructions-grid">
<div class="instruction-card">
<div class="instruction-icon">👆</div>
<h3>食指位置控制星云旋转</h3>
<p>食指指尖在摄像头画面中的位置控制星云旋转方向。向左移动食指,星云向左旋转;向右移动食指,星云向右旋转;向上移动食指,星云向上倾斜;向下移动食指,星云向下倾斜。</p>
</div>

<div class="instruction-card">
<div class="instruction-icon">🤏</div>
<h3>手指捏合控制星云大小</h3>
<p>食指和拇指之间的距离控制星云的缩放比例。捏合手指(手指靠近),星云缩小;展开手指(手指远离),星云放大。可以精细控制星云的大小变化。</p>
</div>

<div class="instruction-card">
<div class="instruction-icon">🖐️</div>
<h3>手腕移动控制粒子躁动</h3>
<p>手腕的移动速度和幅度控制星云粒子的躁动程度。快速移动手腕,粒子会变得活跃、躁动;缓慢移动或保持静止,粒子会恢复平静。可以用来创建动态效果。</p>
</div>
</div>

<div class="hint">
<p>💡 提示:确保手部在摄像头画面中清晰可见,光线充足效果更佳。没有手势时,星云会有自然的微弱运动。</p>
</div>
</div>

<footer class="footer">
<p>使用 MediaPipe Hands 和 Three.js 构建 | 手势控制3D星云 | 优化版</p>
</footer>
</div>

<!-- 隐藏的video元素用于摄像头 -->
<video id="video" class="hidden" autoplay muted playsinline></video>

<!-- 主脚本 -->
<script src="script.js"></script>
</body>
</html>
这是css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', sans-serif;
background: linear-gradient(135deg, #0a0a1a 0%, #151530 100%);
color: #e6e6ff;
line-height: 1.6;
min-height: 100vh;
overflow-x: hidden;
}

.app-container {
display: flex;
flex-direction: column;
min-height: 100vh;
max-width: 100%;
overflow-x: hidden;
}

/* 固定标题区域 */
.header {
text-align: center;
padding: 20px;
background: rgba(10, 15, 35, 0.95);
border-bottom: 1px solid rgba(80, 120, 200, 0.3);
backdrop-filter: blur(10px);
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
}

h1 {
font-size: 2.5rem;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: center;
gap: 15px;
}

.emoji {
font-size: 2.8rem;
filter: drop-shadow(0 0 10px rgba(100, 150, 255, 0.6));
}

.title-text {
color: #b0b0ff;
text-shadow: 0 0 10px rgba(100, 150, 255, 0.5);
}

.subtitle {
font-size: 1.1rem;
color: #b0b0ff;
margin-bottom: 15px;
}

/* 顶部手势图标 */
.top-gestures {
display: flex;
justify-content: center;
gap: 40px;
margin-top: 20px;
flex-wrap: wrap;
}

.gesture-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}

.gesture-icon {
font-size: 2.5rem;
filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.7));
}

.gesture-text {
color: #d0d0ff;
font-size: 0.95rem;
font-weight: 500;
}

/* 主要内容区域 */
.main-content {
display: flex;
height: 60vh;
padding: 20px;
gap: 20px;
flex: 1;
}

.canvas-container {
flex: 1;
background: rgba(10, 15, 35, 0.8);
border-radius: 12px;
border: 1px solid rgba(80, 120, 200, 0.3);
box-shadow: 0 10px 30px rgba(0, 20, 80, 0.3);
overflow: hidden;
display: flex;
flex-direction: column;
}

.canvas-header {
padding: 15px 20px;
background: rgba(20, 25, 50, 0.7);
border-bottom: 1px solid rgba(100, 150, 255, 0.3);
display: flex;
align-items: center;
gap: 12px;
}

.canvas-icon {
font-size: 1.6rem;
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.5));
}

.canvas-title {
font-size: 1.2rem;
color: #b0b0ff;
font-weight: 600;
}

.canvas {
width: 100%;
height: 100%;
background: #000;
display: block;
}

.canvas-footer {
padding: 12px 20px;
background: rgba(20, 25, 50, 0.7);
border-top: 1px solid rgba(100, 150, 255, 0.3);
text-align: center;
color: #b0b0ff;
font-size: 0.9rem;
min-height: 44px;
display: flex;
align-items: center;
justify-content: center;
}

/* 控制按钮区域 */
.controls {
display: flex;
justify-content: center;
gap: 15px;
padding: 15px;
margin-top: 10px;
}

.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 120px;
}

.btn-primary {
background: linear-gradient(135deg, #4169e1, #5d8eff);
color: white;
}

.btn-primary:hover {
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(65, 105, 225, 0.4);
}

.btn-secondary {
background: rgba(60, 65, 100, 0.8);
color: #e0e0ff;
border: 1px solid rgba(100, 150, 255, 0.4);
}

.btn-secondary:hover {
background: rgba(80, 85, 120, 0.9);
transform: translateY(-3px);
box-shadow: 0 8px 20px rgba(100, 150, 255, 0.2);
}

/* 详细说明区域 */
.detailed-instructions {
padding: 30px 20px;
background: rgba(10, 15, 35, 0.9);
border-top: 1px solid rgba(80, 120, 200, 0.3);
margin-top: 20px;
}

.detailed-instructions h2 {
text-align: center;
margin-bottom: 30px;
color: #b0b0ff;
font-size: 1.8rem;
}

.instructions-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 25px;
margin-bottom: 25px;
}

.instruction-card {
background: rgba(20, 25, 50, 0.7);
border-radius: 12px;
padding: 25px;
border: 1px solid rgba(100, 150, 255, 0.3);
transition: all 0.3s ease;
}

.instruction-card:hover {
transform: translateY(-5px);
box-shadow: 0 15px 30px rgba(0, 20, 80, 0.3);
border-color: #6496ff;
}

.instruction-icon {
font-size: 3rem;
margin-bottom: 15px;
text-align: center;
filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.5));
}

.instruction-card h3 {
color: #d0d0ff;
font-size: 1.3rem;
margin-bottom: 15px;
text-align: center;
}

.instruction-card p {
color: #b0b0ff;
font-size: 0.95rem;
line-height: 1.5;
}

.hint {
background: rgba(20, 25, 50, 0.6);
border-radius: 10px;
padding: 15px 20px;
border: 1px solid rgba(100, 150, 255, 0.3);
text-align: center;
max-width: 800px;
margin: 0 auto;
}

.hint p {
color: #d0d0ff;
font-size: 0.95rem;
}

/* 页脚 */
.footer {
text-align: center;
padding: 20px;
background: rgba(10, 15, 35, 0.9);
border-top: 1px solid rgba(80, 120, 200, 0.3);
color: #b0b0ff;
font-size: 0.9rem;
}

/* 隐藏元素 */
.hidden {
display: none;
}

/* 响应式设计 */
@media (max-width: 1100px) {
.main-content {
flex-direction: column;
height: auto;
min-height: 70vh;
}

.canvas-container {
min-height: 350px;
}

.top-gestures {
gap: 20px;
}

.instructions-grid {
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
}

@media (max-width: 768px) {
h1 {
font-size: 2rem;
flex-direction: column;
gap: 10px;
}

.emoji {
font-size: 2.2rem;
}

.subtitle {
font-size: 0.95rem;
}

.top-gestures {
gap: 15px;
}

.gesture-icon {
font-size: 2rem;
}

.gesture-text {
font-size: 0.85rem;
}

.main-content {
padding: 15px;
}

.canvas-header {
padding: 12px 15px;
}

.canvas-title {
font-size: 1.1rem;
}

.controls {
flex-wrap: wrap;
}

.btn {
min-width: 100px;
padding: 10px 20px;
font-size: 0.9rem;
}

.detailed-instructions {
padding: 20px 15px;
}

.detailed-instructions h2 {
font-size: 1.5rem;
margin-bottom: 20px;
}

.instruction-card {
padding: 20px;
}

.instruction-icon {
font-size: 2.5rem;
}

.instruction-card h3 {
font-size: 1.2rem;
}

.instruction-card p {
font-size: 0.9rem;
}
}
这是javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
// 全局变量
let videoElement;
let videoCanvas;
let videoCtx;
let particleCanvas;
let handStatus;
let particleStatus;

// Three.js相关变量
let scene, camera, renderer, particles;
let particleGeometry, particleMaterial;
let particleCount = 12000; // 增加粒子数量
let basePositions = [];

// MediaPipe相关变量
let hands;
let handResults = null;
let lastHandPosition = null;
let lastGestureTime = 0;
let isCameraActive = false;

// 星云控制参数
let scale = 1.0; // 星云大小
let rotationX = 0; // X轴旋转
let rotationY = 0; // Y轴旋转
let agitation = 0; // 躁动强度

// 颜色增强参数 - 大幅增强颜色
let colorIntensity = 2.5; // 颜色强度增强
let saturationBoost = 1.8; // 饱和度增强
let contrastBoost = 1.8; // 对比度增强

// 动画参数
let time = 0;
let animationFrameId = null;

// 初始化函数
function init() {
console.log("开始初始化手势控制3D星云系统...");

// 获取DOM元素
videoElement = document.getElementById('video');
videoCanvas = document.getElementById('video-canvas');
particleCanvas = document.getElementById('particle-canvas');

if (!videoElement || !videoCanvas || !particleCanvas) {
console.error("无法找到必要的HTML元素");
return;
}

videoCtx = videoCanvas.getContext('2d');
handStatus = document.getElementById('hand-status');
particleStatus = document.getElementById('particle-status');

console.log("DOM元素获取成功");

// 初始化Three.js场景
initThreeJSScene();

// 初始化MediaPipe Hands
initMediaPipeHands();

// 设置事件监听器
setupEventListeners();

// 启动动画循环
animate();

console.log("系统初始化完成");
}

// 初始化Three.js场景
function initThreeJSScene() {
try {
console.log("初始化Three.js场景...");

// 创建场景
scene = new THREE.Scene();

// 创建透视相机
const aspect = particleCanvas.clientWidth / particleCanvas.clientHeight;
camera = new THREE.PerspectiveCamera(70, aspect, 0.1, 3000);
camera.position.z = 50;

// 创建渲染器
renderer = new THREE.WebGLRenderer({
canvas: particleCanvas,
antialias: true,
alpha: true,
powerPreference: 'high-performance'
});
renderer.setSize(particleCanvas.clientWidth, particleCanvas.clientHeight);
renderer.setClearColor(0x000000, 1);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));

// 创建粒子系统
createVibrantParticleSystem();

console.log("Three.js场景初始化成功");
particleStatus.textContent = '鲜艳3D星云系统已就绪 - ' + particleCount + '个粒子';

} catch (error) {
console.error("Three.js初始化失败:", error);
particleStatus.textContent = '星云系统初始化失败';
}
}

// 创建鲜艳的粒子系统
function createVibrantParticleSystem() {
try {
console.log("创建鲜艳的粒子系统...");

// 创建粒子几何体
particleGeometry = new THREE.BufferGeometry();

// 重置数组
basePositions = [];

const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
const sizes = new Float32Array(particleCount);

// 创建多个鲜艳的星云核心
const cores = [
{ x: 0, y: 0, z: 0, radius: 25, colorHue: 0.65, density: 0.35 }, // 主核心 - 亮蓝色
{ x: 15, y: 5, z: -10, radius: 15, colorHue: 0.8, density: 0.25 }, // 副核心 - 紫色
{ x: -10, y: -8, z: 8, radius: 12, colorHue: 0.9, density: 0.15 }, // 副核心 - 粉色
{ x: 5, y: 12, z: 15, radius: 10, colorHue: 0.55, density: 0.15 }, // 副核心 - 青色
{ x: -15, y: 10, z: -5, radius: 8, colorHue: 0.3, density: 0.1 } // 副核心 - 橙色
];

let particleIndex = 0;

// 为每个核心创建鲜艳的粒子
for (const core of cores) {
const particlesInCore = Math.floor(particleCount * core.density);

for (let i = 0; i < particlesInCore && particleIndex < particleCount; i++) {
// 在核心内随机分布
const radius = core.radius * Math.pow(Math.random(), 0.5);
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);

const x = core.x + radius * Math.sin(phi) * Math.cos(theta);
const y = core.y + radius * Math.sin(phi) * Math.sin(theta);
const z = core.z + radius * Math.cos(phi);

// 添加一些随机扰动,形成星云形状
const perturb = 3;
const px = x + (Math.random() - 0.5) * perturb;
const py = y + (Math.random() - 0.5) * perturb;
const pz = z + (Math.random() - 0.5) * perturb;

// 保存位置
positions[particleIndex * 3] = px;
positions[particleIndex * 3 + 1] = py;
positions[particleIndex * 3 + 2] = pz;

basePositions.push(px, py, pz);

// 计算到核心的距离,用于颜色和大小
const distance = Math.sqrt(x*x + y*y + z*z) / core.radius;

// 创建鲜艳的颜色 - 大幅增强饱和度和亮度
let hue, saturation, lightness;

// 核心区域 - 更亮更鲜艳
if (distance < 0.3) {
hue = core.colorHue + (Math.random() - 0.5) * 0.08;
saturation = 0.98 + Math.random() * 0.02; // 极高饱和度
lightness = 0.85 + Math.random() * 0.15; // 高亮度
} else if (distance < 0.7) {
hue = core.colorHue + (Math.random() - 0.5) * 0.15;
saturation = 0.9 + Math.random() * 0.08; // 高饱和度
lightness = 0.75 + Math.random() * 0.15; // 中等亮度
} else {
hue = core.colorHue + (Math.random() - 0.5) * 0.25;
saturation = 0.8 + Math.random() * 0.15; // 中等饱和度
lightness = 0.6 + Math.random() * 0.2; // 较低亮度
}

const color = new THREE.Color();
color.setHSL(hue, saturation, lightness);

// 增强颜色强度 - 使颜色更鲜艳
colors[particleIndex * 3] = Math.min(2.0, color.r * colorIntensity);
colors[particleIndex * 3 + 1] = Math.min(2.0, color.g * colorIntensity);
colors[particleIndex * 3 + 2] = Math.min(2.0, color.b * colorIntensity);

// 设置粒子大小 - 核心粒子更大更亮
let size;
if (distance < 0.2) {
size = 0.18 + Math.random() * 0.12; // 核心粒子更大
} else if (distance < 0.5) {
size = 0.12 + Math.random() * 0.08;
} else {
size = 0.08 + Math.random() * 0.06;
}
sizes[particleIndex] = size;

particleIndex++;
}
}

// 添加背景分布的鲜艳星尘粒子
for (; particleIndex < particleCount; particleIndex++) {
// 在更大范围内随机分布
const range = 60;
const px = (Math.random() - 0.5) * range;
const py = (Math.random() - 0.5) * range * 0.7;
const pz = (Math.random() - 0.5) * range;

positions[particleIndex * 3] = px;
positions[particleIndex * 3 + 1] = py;
positions[particleIndex * 3 + 2] = pz;

basePositions.push(px, py, pz);

// 背景粒子颜色 - 各种鲜艳颜色
const hue = Math.random(); // 全色谱范围
const saturation = 0.8 + Math.random() * 0.2; // 高饱和度
const lightness = 0.7 + Math.random() * 0.3; // 较高亮度

const color = new THREE.Color();
color.setHSL(hue, saturation, lightness);

// 增强背景粒子颜色
colors[particleIndex * 3] = Math.min(2.0, color.r * 2.0);
colors[particleIndex * 3 + 1] = Math.min(2.0, color.g * 2.0);
colors[particleIndex * 3 + 2] = Math.min(2.0, color.b * 2.0);

// 背景粒子大小
const size = 0.06 + Math.random() * 0.06;
sizes[particleIndex] = size;
}

// 设置几何体属性
particleGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
particleGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
particleGeometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1));

// 创建自定义着色器材质,实现鲜艳发光效果
const vertexShader = `
attribute float size;
attribute vec3 color;
varying vec3 vColor;
varying float vSize;
uniform float time;
uniform float scale;
uniform float agitation;
uniform float saturationBoost;
uniform float colorIntensity;

// 噪声函数,用于自然运动和躁动效果
float noise(vec3 p) {
return sin(p.x*1.5 + time*0.5) * cos(p.y*1.3 + time*0.3) * sin(p.z*1.7 + time*0.7);
}

void main() {
// 增强颜色饱和度和强度
vec3 enhancedColor = color * colorIntensity * saturationBoost;
vColor = enhancedColor;
vSize = size;

// 应用基础位置
vec3 pos = position;

// 应用缩放
pos *= scale;

// 应用躁动效果
float noiseValue = noise(pos * 0.05 + time);
pos.x += noiseValue * agitation * 3.0;
pos.y += noise(pos.yzx * 0.05 + time) * agitation * 3.0;
pos.z += noise(pos.zxy * 0.05 + time) * agitation * 2.0;

// 添加微弱的自然运动(模拟星云流动)
float naturalMovement = sin(time * 0.2 + position.x * 0.02) * 0.5;
pos.x += naturalMovement;
pos.y += cos(time * 0.3 + position.y * 0.02) * 0.5;
pos.z += sin(time * 0.25 + position.z * 0.02) * 0.3;

vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);

// 粒子大小随躁动变化 - 躁动时粒子更大更亮
float sizeFactor = 1.0 + agitation * 1.5 + sin(time * 0.5 + position.x * 0.01) * 0.5;
gl_PointSize = size * sizeFactor * (500.0 / -mvPosition.z);

gl_Position = projectionMatrix * mvPosition;
}
`;

const fragmentShader = `
varying vec3 vColor;
varying float vSize;

void main() {
// 计算粒子中心的距离
vec2 coord = gl_PointCoord - vec2(0.5);
float distance = length(coord);

// 圆形粒子
if (distance > 0.5) {
discard;
}

// 核心区域更亮 - 根据粒子大小调整
float coreSize = 0.5 - vSize * 0.1;
float core = smoothstep(0.5, coreSize, distance);

// 发光光晕效果 - 更强烈的发光
float glow = pow(1.0 - distance * 2.0, 4.0) * 2.5;

// 边缘发光效果
float edgeGlow = pow(1.0 - distance * 1.3, 3.0) * 2.0;

// 组合效果
float alpha = core * 1.5 + glow * 1.0 + edgeGlow * 0.7;
alpha = min(alpha, 2.0);

// 最终颜色,增强发光效果
vec3 finalColor = vColor + vec3(glow * 1.0) + vec3(edgeGlow * 0.5);
finalColor = min(finalColor, vec3(3.0)); // 限制最大亮度

gl_FragColor = vec4(finalColor, alpha);
}
`;

particleMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 },
scale: { value: scale },
agitation: { value: agitation },
saturationBoost: { value: saturationBoost },
colorIntensity: { value: colorIntensity }
},
vertexShader: vertexShader,
fragmentShader: fragmentShader,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false
});

// 创建粒子系统
particles = new THREE.Points(particleGeometry, particleMaterial);
scene.add(particles);

console.log("鲜艳粒子系统创建成功");

} catch (error) {
console.error("创建粒子系统失败:", error);
particleStatus.textContent = '创建粒子系统失败';
}
}

// 初始化MediaPipe Hands
function initMediaPipeHands() {
try {
console.log("初始化MediaPipe Hands...");

// 检查MediaPipe是否加载
if (typeof window.Hands === 'undefined') {
console.error("MediaPipe Hands未加载");
handStatus.textContent = "MediaPipe库加载失败,请刷新页面";
return;
}

hands = new Hands({
locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;
}
});

hands.setOptions({
maxNumHands: 1,
modelComplexity: 0,
minDetectionConfidence: 0.6,
minTrackingConfidence: 0.6
});

hands.onResults(onHandResults);

console.log("MediaPipe Hands初始化成功");
handStatus.textContent = "手部检测系统就绪";

} catch (error) {
console.error("MediaPipe初始化失败:", error);
handStatus.textContent = "手部检测系统初始化失败";
}
}

// 处理手部检测结果 - 修复:绘制原始摄像头画面
function onHandResults(results) {
if (!results) {
return;
}

handResults = results;

// 绘制摄像头画面 - 修复:确保绘制原始彩色画面
if (results.image && videoCtx) {
videoCtx.save();
videoCtx.clearRect(0, 0, videoCanvas.width, videoCanvas.height);
// 绘制原始摄像头画面(彩色)
videoCtx.drawImage(results.image, 0, 0, videoCanvas.width, videoCanvas.height);
videoCtx.restore();
}

// 如果没有检测到手部
if (!results.multiHandLandmarks || results.multiHandLandmarks.length === 0) {
handStatus.textContent = '未检测到手部,请将手放在摄像头前';

// 缓慢重置参数,但保持轻微运动
scale = scale * 0.98 + 1.0 * 0.02;
rotationX *= 0.95;
rotationY *= 0.95;
agitation *= 0.95;

return;
}

// 检测到手部
handStatus.textContent = '手部已检测到,正在分析手势...';

// 绘制手部关键点(在彩色画面上叠加绘制)
if (results.multiHandLandmarks && videoCtx) {
for (const landmarks of results.multiHandLandmarks) {
drawHand(landmarks);
processHandGesture(landmarks);
}
}
}

// 绘制手部
function drawHand(landmarks) {
if (!videoCtx) return;

videoCtx.save();

// 绘制连接线
videoCtx.strokeStyle = '#00FF00';
videoCtx.lineWidth = 2;

// 绘制手部连接线
const connections = [
[0, 1], [1, 2], [2, 3], [3, 4], // 拇指
[0, 5], [5, 6], [6, 7], [7, 8], // 食指
[0, 9], [9, 10], [10, 11], [11, 12], // 中指
[0, 13], [13, 14], [14, 15], [15, 16], // 无名指
[0, 17], [17, 18], [18, 19], [19, 20], // 小指
[5, 9], [9, 13], [13, 17] // 手掌
];

for (const [start, end] of connections) {
const startPoint = landmarks[start];
const endPoint = landmarks[end];

videoCtx.beginPath();
videoCtx.moveTo(startPoint.x * videoCanvas.width, startPoint.y * videoCanvas.height);
videoCtx.lineTo(endPoint.x * videoCanvas.width, endPoint.y * videoCanvas.height);
videoCtx.stroke();
}

// 绘制关键点(特别标记食指和拇指指尖)
for (let i = 0; i < landmarks.length; i++) {
const landmark = landmarks[i];
let color = '#FFFF00'; // 默认黄色

if (i === 0) {
color = '#ff1493'; // 手腕粉色
} else if ([4, 8, 12, 16, 20].includes(i)) {
color = '#00ffff'; // 指尖青色
}

// 特别标记食指指尖(8)和拇指指尖(4)
if (i === 8) {
color = '#ff00ff'; // 食指指尖品红色
} else if (i === 4) {
color = '#ffff00'; // 拇指指尖亮黄色
}

videoCtx.fillStyle = color;
videoCtx.beginPath();
videoCtx.arc(
landmark.x * videoCanvas.width,
landmark.y * videoCanvas.height,
(i === 0) ? 6 : (i === 8 || i === 4) ? 8 : 4,
0,
Math.PI * 2
);
videoCtx.fill();

// 为食指和拇指指尖添加发光效果
if (i === 8 || i === 4) {
videoCtx.shadowColor = color;
videoCtx.shadowBlur = 15;
videoCtx.fill();
videoCtx.shadowBlur = 0;
}
}

videoCtx.restore();
}

// 处理手势数据 - 核心控制映射
function processHandGesture(landmarks) {
if (!landmarks || landmarks.length < 21) return;

const now = Date.now();
const wrist = landmarks[0]; // 手腕
const thumbTip = landmarks[4]; // 拇指指尖
const indexTip = landmarks[8]; // 食指指尖

// 控制映射1: 食指指尖坐标映射为星云旋转角度
// 将归一化的屏幕坐标映射到旋转角度
rotationY = (indexTip.x - 0.5) * 4; // -2 到 2 的范围
rotationX = (0.5 - indexTip.y) * 2; // -1 到 1 的范围

// 控制映射2: 食指与拇指指尖距离映射为星云缩放
// 计算食指和拇指指尖之间的距离
const thumbIndexDistance = Math.sqrt(
Math.pow(thumbTip.x - indexTip.x, 2) +
Math.pow(thumbTip.y - indexTip.y, 2)
);

// 归一化距离并映射到缩放范围 0.5 到 3.0
scale = 0.5 + thumbIndexDistance * 2.5;
scale = Math.max(0.5, Math.min(3.0, scale));

// 控制映射3: 手腕移动速度映射为粒子躁动强度
if (lastHandPosition) {
const deltaTime = (now - lastGestureTime) / 1000;
if (deltaTime > 0) {
const deltaX = wrist.x - lastHandPosition.x;
const deltaY = wrist.y - lastHandPosition.y;
const movementSpeed = Math.sqrt(deltaX * deltaX + deltaY * deltaY) / deltaTime;

// 将速度映射到躁动强度 0 到 2
agitation = Math.min(2.0, movementSpeed * 5);
}
lastGestureTime = now;
}

lastHandPosition = { x: wrist.x, y: wrist.y };
}

// 设置事件监听器
function setupEventListeners() {
console.log("设置事件监听器...");

// 窗口大小改变时更新画布尺寸
window.addEventListener('resize', onWindowResize);

// 按钮事件监听
document.getElementById('start-btn').addEventListener('click', startCamera);
document.getElementById('stop-btn').addEventListener('click', stopCamera);
document.getElementById('reset-btn').addEventListener('click', resetParticles);

// 初始化窗口大小
onWindowResize();

console.log("事件监听器设置完成");
}

// 窗口大小改变处理
function onWindowResize() {
if (particleCanvas && renderer && camera) {
const width = particleCanvas.parentElement.clientWidth;
const height = particleCanvas.parentElement.clientHeight;

particleCanvas.width = width;
particleCanvas.height = height;

renderer.setSize(width, height);
camera.aspect = width / height;
camera.updateProjectionMatrix();
}

if (videoCanvas) {
const width = videoCanvas.parentElement.clientWidth;
const height = videoCanvas.parentElement.clientHeight;

videoCanvas.width = width;
videoCanvas.height = height;
}
}

// 启动摄像头
async function startCamera() {
try {
console.log("正在启动摄像头...");
handStatus.textContent = '正在请求摄像头权限...';

if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
throw new Error("您的浏览器不支持摄像头访问");
}

const stream = await navigator.mediaDevices.getUserMedia({
video: {
width: { ideal: 640 },
height: { ideal: 480 },
facingMode: 'user',
frameRate: { ideal: 30 }
},
audio: false
});

console.log("摄像头权限已获得");

videoElement.srcObject = stream;
handStatus.textContent = '摄像头已启动,请将手放在摄像头前';

await new Promise((resolve) => {
videoElement.onloadedmetadata = () => {
videoElement.play().then(resolve).catch(() => resolve());
};
});

isCameraActive = true;
processVideoFrame();

console.log("摄像头启动成功");

} catch (error) {
console.error('摄像头访问错误:', error);
handStatus.textContent = '无法访问摄像头';

let errorMessage = '无法访问摄像头。请确保已授予摄像头权限。';
if (error.name === 'NotAllowedError') {
errorMessage = '摄像头访问被拒绝。请允许摄像头权限并刷新页面。';
} else if (error.name === 'NotFoundError') {
errorMessage = '未找到摄像头设备。请检查摄像头连接。';
} else if (error.name === 'NotReadableError') {
errorMessage = '摄像头正被其他应用占用。';
}

alert(errorMessage);
}
}

// 处理视频帧
async function processVideoFrame() {
if (!isCameraActive) return;

try {
if (videoElement.readyState < 2) {
setTimeout(() => processVideoFrame(), 100);
return;
}

const tempCanvas = document.createElement('canvas');
tempCanvas.width = videoElement.videoWidth || 640;
tempCanvas.height = videoElement.videoHeight || 480;
const tempCtx = tempCanvas.getContext('2d');

// 确保绘制彩色图像
tempCtx.drawImage(videoElement, 0, 0, tempCanvas.width, tempCanvas.height);

if (hands && typeof hands.send === 'function') {
await hands.send({image: tempCanvas});
}

setTimeout(() => processVideoFrame(), 50);
} catch (error) {
console.error('处理视频帧时出错:', error);
setTimeout(() => processVideoFrame(), 100);
}
}

// 停止摄像头
function stopCamera() {
console.log("停止摄像头");
isCameraActive = false;

if (videoElement.srcObject) {
const tracks = videoElement.srcObject.getTracks();
tracks.forEach(track => track.stop());
videoElement.srcObject = null;
}

handStatus.textContent = '摄像头已停止';

if (videoCtx) {
videoCtx.clearRect(0, 0, videoCanvas.width, videoCanvas.height);
// 显示提示文字
videoCtx.fillStyle = '#ffffff';
videoCtx.font = '20px Arial';
videoCtx.textAlign = 'center';
videoCtx.fillText('摄像头已停止', videoCanvas.width/2, videoCanvas.height/2);
}

handResults = null;

// 重置参数
scale = 1.0;
rotationX = 0;
rotationY = 0;
agitation = 0;
}

// 重置粒子系统
function resetParticles() {
console.log("重置粒子系统");

// 重新创建粒子系统
if (scene && particles) {
scene.remove(particles);
createVibrantParticleSystem();
}

// 重置控制参数
scale = 1.0;
rotationX = 0;
rotationY = 0;
agitation = 0;
time = 0;

particleStatus.textContent = '星云已重置为鲜艳状态';
}

// 动画循环
function animate(timestamp) {
requestAnimationFrame(animate);

// 更新时间
time = timestamp * 0.001;

// 更新材质uniforms
if (particleMaterial && particleMaterial.uniforms) {
particleMaterial.uniforms.time.value = time;
particleMaterial.uniforms.scale.value = scale;
particleMaterial.uniforms.agitation.value = agitation;
particleMaterial.uniforms.saturationBoost.value = saturationBoost;
particleMaterial.uniforms.colorIntensity.value = colorIntensity;
}

// 应用旋转
if (particles) {
particles.rotation.y += rotationY * 0.01;
particles.rotation.x += rotationX * 0.01;

// 即使没有手势,也添加微弱的自然旋转
if (Math.abs(rotationY) < 0.1 && Math.abs(rotationX) < 0.1) {
particles.rotation.y += 0.01;
particles.rotation.x += 0.005 * Math.sin(time * 0.1);
}

// 添加轻微的脉动效果,使星云更有生气
particles.rotation.z += 0.002 * Math.sin(time * 0.3);
}

// 渲染Three.js场景
if (renderer && scene && camera) {
renderer.render(scene, camera);
}
}

// 页面加载完成后初始化
window.addEventListener('DOMContentLoaded', () => {
console.log("DOM加载完成,开始初始化");
init();
});

2025.12.17

never_gonna_give_you_up伪装成deepseek

我在看CS50的时候,老师讲到了一个事情:
html中写网址,如果实际是网址A,而本该是文字描述的地方写成网址B,那么就会唬住不仔细的人,就有可能实现钓鱼。
比如不这么写

1
<a href="https://www.bilibili.com/">这是哔哩哔哩</a >

而是写成

1
<a href="https://www.bilibili.com/">www.example.com</a >

那就有一定的伪装性了

诶,我也要做
于是,我就新建了个html文件,写了这些内容

这是我写的测试.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
a
{
text-decoration: none;
}
a:hover
{
text-decoration:underline;
}
</style>
<title>我做的toy</title>
</head>
<body>
<h2>
<p style="text-align:center">
点击进入deepseek官网
</p>
</h2>

<h3>
<p style="text-align:center">
<a href="https://www.bilibili.com/video/BV1GJ411x7h7/">https://chat.deepseek.com/</a>
</p>
</h3>
</body>
</html>

打开便是这个效果

测试.html

而如果我点击那个所谓的deepseek链接

就会看见…

你会看见...

哈哈,还蛮有意思的


2025.12.27

粒子小游戏

那天我在群里和同学闲聊

同学跟deepseek要了一段很酷的代码,让我运行着玩

python代码在这里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
import pygame
import random
import math
import sys

# 初始化Pygame
pygame.init()

# 屏幕设置
WIDTH, HEIGHT = 1200, 800
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("炫酷粒子系统模拟")

# 颜色定义
BLACK = (0, 0, 0)
COLORS = [
(255, 50, 50), # 红色
(50, 255, 50), # 绿色
(50, 50, 255), # 蓝色
(255, 255, 50), # 黄色
(255, 50, 255), # 紫色
(50, 255, 255), # 青色
(255, 150, 50), # 橙色
(200, 50, 255), # 粉紫
]

# 粒子类
class Particle:
def __init__(self, x, y):
self.x = x
self.y = y
self.size = random.randint(2, 6)
self.color = random.choice(COLORS)
self.speed_x = random.uniform(-2, 2)
self.speed_y = random.uniform(-2, 2)
self.life = random.randint(100, 255)
self.gravity = 0.05
self.trail = []
self.max_trail_length = 10

def update(self):
# 保存轨迹点
self.trail.append((self.x, self.y))
if len(self.trail) > self.max_trail_length:
self.trail.pop(0)

# 更新位置
self.x += self.speed_x
self.y += self.speed_y

# 应用重力
self.speed_y += self.gravity

# 随机运动变化
self.speed_x += random.uniform(-0.1, 0.1)
self.speed_y += random.uniform(-0.1, 0.1)

# 限制速度
speed = math.sqrt(self.speed_x**2 + self.speed_y**2)
if speed > 5:
self.speed_x = (self.speed_x / speed) * 5
self.speed_y = (self.speed_y / speed) * 5

# 边界反弹
if self.x <= 0 or self.x >= WIDTH:
self.speed_x *= -0.8
self.x = max(0, min(WIDTH, self.x))
if self.y <= 0 or self.y >= HEIGHT:
self.speed_y *= -0.8
self.y = max(0, min(HEIGHT, self.y))

# 生命衰减
self.life -= 1

# 粒子逐渐变小
self.size = max(0.5, self.size * 0.99)

def draw(self, surface):
# 绘制轨迹
for i, (trail_x, trail_y) in enumerate(self.trail):
alpha = int(self.life * (i / len(self.trail)))
size = self.size * (i / len(self.trail))
if size > 0:
trail_color = (
min(255, self.color[0] + 50),
min(255, self.color[1] + 50),
min(255, self.color[2] + 50),
alpha
)
pygame.draw.circle(
surface,
trail_color,
(int(trail_x), int(trail_y)),
max(0.5, size)
)

# 绘制粒子
pygame.draw.circle(
surface,
self.color,
(int(self.x), int(self.y)),
max(0.5, self.size)
)

# 绘制光晕效果
glow_surface = pygame.Surface((int(self.size * 6), int(self.size * 6)), pygame.SRCALPHA)
pygame.draw.circle(
glow_surface,
(*self.color, 30),
(int(self.size * 3), int(self.size * 3)),
self.size * 3
)
surface.blit(glow_surface, (int(self.x - self.size * 3), int(self.y - self.size * 3)), special_flags=pygame.BLEND_ALPHA_SDL2)

# 粒子系统管理器
class ParticleSystem:
def __init__(self):
self.particles = []
self.gradient_bg = self.create_gradient()

def create_gradient(self):
"""创建渐变色背景"""
gradient = pygame.Surface((WIDTH, HEIGHT))
for y in range(HEIGHT):
# 从深蓝色到黑色的渐变
color_value = int(10 + (y / HEIGHT) * 20)
pygame.draw.line(gradient, (color_value//3, color_value//6, color_value), (0, y), (WIDTH, y))
return gradient

def add_particle(self, x, y):
self.particles.append(Particle(x, y))

def add_explosion(self, x, y, count=30):
for _ in range(count):
self.particles.append(Particle(x, y))

def update(self):
# 更新所有粒子
for particle in self.particles[:]:
particle.update()
if particle.life <= 0 or particle.size <= 0.2:
self.particles.remove(particle)

def draw(self, surface):
# 绘制渐变背景
surface.blit(self.gradient_bg, (0, 0))

# 绘制所有粒子
for particle in self.particles:
particle.draw(surface)

# 绘制统计信息
font = pygame.font.SysFont(None, 24)
stats_text = f"粒子数量: {len(self.particles)}"
text_surface = font.render(stats_text, True, (200, 200, 200))
surface.blit(text_surface, (10, 10))

# 绘制说明文字
instructions = [
"鼠标点击: 生成粒子",
"鼠标拖拽: 生成粒子流",
"空格键: 生成爆炸效果",
"R键: 重置粒子系统",
"ESC键: 退出"
]

for i, instruction in enumerate(instructions):
text = font.render(instruction, True, (200, 200, 200))
surface.blit(text, (WIDTH - 250, 20 + i * 25))

# 主函数
def main():
clock = pygame.time.Clock()
particle_system = ParticleSystem()
running = True
mouse_down = False

# 初始爆炸效果
for _ in range(5):
particle_system.add_explosion(
random.randint(100, WIDTH-100),
random.randint(100, HEIGHT-100),
random.randint(20, 40)
)

while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False

elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_SPACE:
# 在随机位置生成爆炸
for _ in range(3):
particle_system.add_explosion(
random.randint(100, WIDTH-100),
random.randint(100, HEIGHT-100),
random.randint(20, 40)
)
elif event.key == pygame.K_r:
# 重置粒子系统
particle_system.particles.clear()

elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_down = True
if event.button == 1: # 左键
particle_system.add_explosion(event.pos[0], event.pos[1], 30)
elif event.button == 3: # 右键
# 右键创建持续粒子流
for _ in range(50):
particle_system.add_particle(event.pos[0], event.pos[1])

elif event.type == pygame.MOUSEBUTTONUP:
mouse_down = False

elif event.type == pygame.MOUSEMOTION and mouse_down:
# 鼠标拖拽时生成粒子
particle_system.add_particle(event.pos[0], event.pos[1])

# 随机生成一些粒子
if random.random() < 0.1 and len(particle_system.particles) < 500:
particle_system.add_particle(
random.randint(0, WIDTH),
random.randint(0, 50)
)

# 更新粒子系统
particle_system.update()

# 绘制所有内容
particle_system.draw(screen)

# 更新显示
pygame.display.flip()
clock.tick(60)

pygame.quit()
sys.exit()

if __name__ == "__main__":
main()

我打开Visual Studio,点击运行,报错了。没有pygame

我尝试pip pygame,发现我的Python3.14太新了,难以兼容

于是我又问deepseek,这个代码,还有没有别的实现方式

于是便改成了HTML

点击查看HTML代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>炫酷粒子系统 (Web版)</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: #000;
font-family: 'Segoe UI', 'Microsoft YaHei', sans-serif;
color: #ccc;
}
#canvasContainer {
position: relative;
width: 100vw;
height: 100vh;
}
#particleCanvas {
display: block;
background: radial-gradient(ellipse at center, #0a0a2a 0%, #000000 100%);
}
#uiPanel {
position: absolute;
top: 20px;
left: 20px;
background: rgba(20, 20, 40, 0.8);
padding: 15px;
border-radius: 10px;
border: 1px solid rgba(100, 100, 255, 0.3);
max-width: 300px;
backdrop-filter: blur(5px);
}
h2, h3 {
color: #6cf;
margin-bottom: 10px;
}
.control-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-size: 0.9em;
}
input[type="range"] {
width: 100%;
margin-bottom: 5px;
}
.value-display {
text-align: right;
font-size: 0.8em;
color: #8af;
}
#stats {
position: absolute;
bottom: 20px;
left: 20px;
background: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 5px;
}
</style>
</head>
<body>
<div id="canvasContainer">
<canvas id="particleCanvas"></canvas>
<div id="uiPanel">
<h2>⚡ 粒子系统控制台</h2>
<div class="control-group">
<h3>粒子行为</h3>
<label for="gravitySlider">重力强度: <span id="gravityValue">0.05</span></label>
<input type="range" id="gravitySlider" min="0" max="0.2" step="0.01" value="0.05">

<label for="speedSlider">初始速度: <span id="speedValue">2.0</span></label>
<input type="range" id="speedSlider" min="0.5" max="5" step="0.1" value="2.0">

<label for="trailSlider">轨迹长度: <span id="trailValue">10</span></label>
<input type="range" id="trailSlider" min="1" max="20" step="1" value="10">
</div>
<div class="control-group">
<h3>交互生成</h3>
<button onclick="createExplosion(canvas.width/2, canvas.height/2, 50)">中心爆炸</button>
<button onclick="particles = []">清除所有粒子</button>
<p style="margin-top:10px; font-size:0.8em;">
操作提示:<br>
• 鼠标点击:生成爆炸<br>
• 鼠标拖拽:生成粒子流<br>
• 自动边缘生成:已开启
</p>
</div>
</div>
<div id="stats">
粒子数量: <span id="particleCount">0</span> |
帧率: <span id="fps">0</span> FPS
</div>
</div>

<script>
// 获取Canvas上下文
const canvas = document.getElementById('particleCanvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

// 粒子颜色池
const PARTICLE_COLORS = [
'#ff3232', '#32ff32', '#3232ff', '#ffff32',
'#ff32ff', '#32ffff', '#ff9632', '#c832ff'
];

// 核心粒子类
class Particle {
constructor(x, y) {
this.x = x;
this.y = y;
this.size = Math.random() * 4 + 2;
this.color = PARTICLE_COLORS[Math.floor(Math.random() * PARTICLE_COLORS.length)];
this.speedX = (Math.random() - 0.5) * parseFloat(document.getElementById('speedSlider').value);
this.speedY = (Math.random() - 0.5) * parseFloat(document.getElementById('speedSlider').value);
this.life = 1.0; // 完全生命值
this.decay = Math.random() * 0.01 + 0.005; // 衰减速度
this.trail = [];
this.maxTrailLength = parseInt(document.getElementById('trailSlider').value);
this.gravity = parseFloat(document.getElementById('gravitySlider').value);
}

update() {
// 保存轨迹
this.trail.push({x: this.x, y: this.y});
if (this.trail.length > this.maxTrailLength) this.trail.shift();

// 应用物理
this.x += this.speedX;
this.y += this.speedY;
this.speedY += this.gravity;
this.speedX += (Math.random() - 0.5) * 0.1;
this.speedY += (Math.random() - 0.5) * 0.1;

// 边界碰撞(带能量损失)
if (this.x <= 0 || this.x >= canvas.width) {
this.speedX *= -0.8;
this.x = this.x <= 0 ? 1 : canvas.width - 1;
}
if (this.y <= 0 || this.y >= canvas.height) {
this.speedY *= -0.8;
this.y = this.y <= 0 ? 1 : canvas.height - 1;
}

// 生命衰减
this.life -= this.decay;
this.size *= 0.99;
return this.life > 0 && this.size > 0.2;
}

draw() {
// 绘制轨迹
for (let i = 0; i < this.trail.length; i++) {
const point = this.trail[i];
const alpha = this.life * (i / this.trail.length);
const size = this.size * (i / this.trail.length);

ctx.beginPath();
ctx.arc(point.x, point.y, Math.max(0.5, size), 0, Math.PI * 2);
ctx.fillStyle = this.hexToRgba(this.color, alpha);
ctx.fill();
}

// 绘制主粒子
ctx.beginPath();
ctx.arc(this.x, this.y, Math.max(0.5, this.size), 0, Math.PI * 2);
ctx.fillStyle = this.color;
ctx.fill();

// 光晕效果
ctx.beginPath();
ctx.arc(this.x, this.y, this.size * 3, 0, Math.PI * 2);
ctx.fillStyle = this.hexToRgba(this.color, 0.15);
ctx.fill();
}

hexToRgba(hex, alpha) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
}

// 粒子系统
let particles = [];
let mouseX = 0, mouseY = 0, isMouseDown = false;
let frameCount = 0, lastTime = performance.now(), fps = 0;

// 生成爆炸
function createExplosion(x, y, count) {
for (let i = 0; i < count; i++) {
particles.push(new Particle(x, y));
}
}

// 初始化随机爆炸
for (let i = 0; i < 5; i++) {
setTimeout(() => {
createExplosion(
Math.random() * canvas.width * 0.6 + canvas.width * 0.2,
Math.random() * canvas.height * 0.6 + canvas.height * 0.2,
Math.floor(Math.random() * 30) + 20
);
}, i * 200);
}

// 事件监听
canvas.addEventListener('mousedown', (e) => {
isMouseDown = true;
createExplosion(e.clientX, e.clientY, 30);
});
canvas.addEventListener('mouseup', () => isMouseDown = false);
canvas.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
if (isMouseDown) particles.push(new Particle(mouseX, mouseY));
});
window.addEventListener('resize', () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
});

// 滑块事件绑定
document.getElementById('gravitySlider').addEventListener('input', function() {
document.getElementById('gravityValue').textContent = this.value;
});
document.getElementById('speedSlider').addEventListener('input', function() {
document.getElementById('speedValue').textContent = this.value;
});
document.getElementById('trailSlider').addEventListener('input', function() {
document.getElementById('trailValue').textContent = this.value;
});

// 主动画循环
function animate(currentTime) {
// 计算FPS
frameCount++;
if (currentTime - lastTime >= 1000) {
fps = Math.round((frameCount * 1000) / (currentTime - lastTime));
frameCount = 0;
lastTime = currentTime;
}

// 清屏(使用半透明黑色制造拖尾效果)
ctx.fillStyle = 'rgba(10, 10, 30, 0.1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);

// 随机生成新粒子(如果数量不多)
if (Math.random() < 0.1 && particles.length < 500) {
particles.push(new Particle(
Math.random() * canvas.width,
Math.random() * -50
));
}

// 更新并绘制所有粒子
for (let i = particles.length - 1; i >= 0; i--) {
if (!particles[i].update()) {
particles.splice(i, 1);
} else {
particles[i].draw();
}
}

// 更新UI显示
document.getElementById('particleCount').textContent = particles.length;
document.getElementById('fps').textContent = fps;

requestAnimationFrame(animate);
}

// 启动动画
animate(performance.now());
</script>
</body>
</html>

效果是这样的

效果在这里

先前我没细看代码(其实细看也看不太懂
所以点开之后,我还挺震惊的
小东西挺有意思啊👍