Created
March 7, 2025 13:50
-
-
Save nicekate/89d76b3aecfded678edf396488d323ff to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="zh"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>NiceKate AI 炫目动画</title> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@700&display=swap'); | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| overflow: hidden; | |
| background: #0c1633; | |
| font-family: 'Orbitron', sans-serif; | |
| } | |
| .typewriter-container { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| z-index: 100; | |
| text-align: center; | |
| filter: drop-shadow(0 0 25px rgba(0, 195, 255, 0.8)); | |
| } | |
| .typewriter { | |
| color: white; | |
| font-size: 5.5em; | |
| font-weight: 700; | |
| letter-spacing: 5px; | |
| margin: 0; | |
| padding: 0; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| position: relative; | |
| } | |
| .typewriter span { | |
| display: inline-block; | |
| position: relative; | |
| transform-style: preserve-3d; | |
| perspective: 500px; | |
| animation: glowPulse 3s infinite; | |
| } | |
| .cursor { | |
| display: inline-block; | |
| width: 6px; | |
| height: 1em; | |
| background-color: white; | |
| margin-left: 5px; | |
| animation: blink 1s infinite; | |
| box-shadow: 0 0 15px #00c3ff, 0 0 30px #00c3ff; | |
| } | |
| @keyframes blink { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0; } | |
| } | |
| @keyframes glowPulse { | |
| 0% { text-shadow: 0 0 10px rgba(0, 195, 255, 0.8), 0 0 20px rgba(0, 195, 255, 0.5); } | |
| 50% { text-shadow: 0 0 20px rgba(0, 195, 255, 1), 0 0 30px rgba(0, 195, 255, 0.8), 0 0 40px rgba(0, 195, 255, 0.6); } | |
| 100% { text-shadow: 0 0 10px rgba(0, 195, 255, 0.8), 0 0 20px rgba(0, 195, 255, 0.5); } | |
| } | |
| @keyframes characterEntrance { | |
| 0% { | |
| opacity: 0; | |
| transform: translateY(20px) scale(0.8); | |
| filter: blur(10px); | |
| } | |
| 100% { | |
| opacity: 1; | |
| transform: translateY(0) scale(1); | |
| filter: blur(0); | |
| } | |
| } | |
| @keyframes characterExit { | |
| 0% { | |
| opacity: 1; | |
| transform: translateY(0) scale(1); | |
| filter: blur(0); | |
| } | |
| 100% { | |
| opacity: 0; | |
| transform: translateY(-20px) scale(0.8); | |
| filter: blur(10px); | |
| } | |
| } | |
| /* 霓虹效果 */ | |
| .neon-glow { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| z-index: -1; | |
| background: radial-gradient( | |
| circle at center, | |
| rgba(0, 195, 255, 0.2) 0%, | |
| rgba(0, 195, 255, 0.1) 20%, | |
| rgba(0, 11, 22, 0) 70% | |
| ); | |
| opacity: 0; | |
| transition: opacity 0.5s ease; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="typewriter-container"> | |
| <div class="neon-glow" id="neon-glow"></div> | |
| <h1 class="typewriter" id="typewriter"></h1> | |
| <span class="cursor"></span> | |
| </div> | |
| <script> | |
| // 粒子数组 | |
| let particles = []; | |
| const particleCount = 150; // 增加粒子数量 | |
| // 添加特殊的焦点粒子 | |
| let focalPoints = []; | |
| // 文字围绕粒子 | |
| let textParticles = []; | |
| // 打字机效果变量 | |
| const text = "NiceKate AI"; | |
| let currentIndex = 0; | |
| let typingSpeed = 45; // 整体打字速度 | |
| let typingStarted = false; | |
| let typingCompleted = false; | |
| let typewriterElement = null; | |
| let neonGlow = null; | |
| let charSpans = []; | |
| // 文字动画状态 | |
| const TYPING = 'typing'; | |
| const DISPLAY = 'display'; | |
| const DELETING = 'deleting'; | |
| const WAITING = 'waiting'; | |
| let textAnimState = TYPING; | |
| let stateTimer = 0; | |
| let displayTime = 2500; // 完整显示时间(毫秒) | |
| let waitingTime = 600; // 等待时间(毫秒) | |
| // 性能优化参数 | |
| let lastFrameTime = 0; | |
| const targetFrameRate = 60; | |
| const frameInterval = 1000 / targetFrameRate; | |
| // 配色方案 | |
| const colorSchemes = [ | |
| // 紫蓝方案 | |
| [ | |
| [65, 105, 225, 180], // 蓝色基调 | |
| [138, 43, 226, 180], // 紫色 | |
| [220, 220, 255, 180] // 亮紫色 | |
| ], | |
| // 青绿方案 | |
| [ | |
| [0, 255, 255, 180], // 青色 | |
| [50, 205, 50, 180], // 绿色 | |
| [230, 255, 250, 180] // 亮青色 | |
| ], | |
| // 红橙方案 | |
| [ | |
| [255, 69, 0, 180], // 红色 | |
| [255, 140, 0, 180], // 橙色 | |
| [255, 222, 173, 180] // 金色 | |
| ] | |
| ]; | |
| // 选择一个配色方案 | |
| let activeColorScheme = 0; | |
| function setup() { | |
| // 创建全屏画布 | |
| createCanvas(windowWidth, windowHeight); | |
| // 初始化粒子 | |
| for (let i = 0; i < particleCount; i++) { | |
| particles.push(new Particle()); | |
| } | |
| // 创建焦点,粒子会往这些点靠拢 | |
| for (let i = 0; i < 5; i++) { | |
| focalPoints.push({ | |
| pos: createVector(random(width), random(height)), | |
| vel: createVector(random(-0.3, 0.3), random(-0.3, 0.3)), | |
| life: random(300, 600) // 焦点寿命 | |
| }); | |
| } | |
| // 获取HTML元素 | |
| typewriterElement = document.getElementById('typewriter'); | |
| neonGlow = document.getElementById('neon-glow'); | |
| // 启动打字机效果,立即开始 | |
| setTimeout(startTypewriter, 500); | |
| // 创建文字围绕粒子 | |
| for (let i = 0; i < 50; i++) { | |
| textParticles.push(new TextParticle()); | |
| } | |
| // 定期更换配色方案 | |
| setInterval(() => { | |
| activeColorScheme = (activeColorScheme + 1) % colorSchemes.length; | |
| // 当配色变化时,给部分粒子更新颜色 | |
| for (let i = 0; i < particles.length; i++) { | |
| if (random() < 0.3) { // 30%的粒子立即更新颜色 | |
| particles[i].updateColor(); | |
| } | |
| } | |
| }, 8000); // 每8秒更换配色 | |
| } | |
| function startTypewriter() { | |
| typingStarted = true; | |
| textAnimState = TYPING; | |
| // 清空现有内容 | |
| typewriterElement.innerHTML = ''; | |
| charSpans = []; | |
| // 预先创建所有字符,但设置为不可见 | |
| for (let i = 0; i < text.length; i++) { | |
| const charSpan = document.createElement('span'); | |
| charSpan.textContent = text.charAt(i); | |
| charSpan.style.opacity = '0'; | |
| // 字符颜色变化较大 | |
| const hue = 180 + Math.random() * 60; // 180-240范围的色调 | |
| const saturation = 90 + Math.random() * 10; // 90-100%饱和度 | |
| const lightness = 70 + Math.random() * 20; // 70-90%亮度 | |
| charSpan.style.color = `hsl(${hue}, ${saturation}%, ${lightness}%)`; | |
| typewriterElement.appendChild(charSpan); | |
| charSpans.push(charSpan); | |
| } | |
| // 开始显示字符 | |
| // 立即显示所有字符,没有递归延迟 | |
| for (let i = 0; i < text.length - 2; i++) { | |
| setTimeout(() => { | |
| const charSpan = charSpans[i]; | |
| charSpan.style.animation = 'characterEntrance 0.2s forwards'; | |
| // 更新霓虹辉光效果 | |
| neonGlow.style.opacity = Math.min(0.7, (i + 1) / text.length); | |
| }, i * typingSpeed); | |
| } | |
| // 给"AI"部分一个特殊的更短延迟 | |
| const aiStartIndex = text.length - 2; | |
| setTimeout(() => { | |
| // 同时显示"AI"两个字符 | |
| for (let i = aiStartIndex; i < text.length; i++) { | |
| const charSpan = charSpans[i]; | |
| charSpan.style.animation = 'characterEntrance 0.15s forwards'; | |
| } | |
| // 完成时最大化霓虹辉光效果 | |
| neonGlow.style.opacity = '1'; | |
| // 立即更新状态,不延迟 | |
| currentIndex = text.length; | |
| typingCompleted = true; | |
| textAnimState = DISPLAY; | |
| stateTimer = millis(); | |
| }, (text.length - 2) * typingSpeed + 50); | |
| } | |
| function showNextCharacter(index) { | |
| if (index < text.length) { | |
| const charSpan = charSpans[index]; | |
| charSpan.style.animation = 'characterEntrance 0.2s forwards'; | |
| // 逐渐增加霓虹辉光效果 | |
| neonGlow.style.opacity = Math.min(1, (index + 1) / text.length); | |
| // 为"AI"大幅加快显示速度 | |
| let nextDelay = typingSpeed; | |
| if (index >= text.length - 2) { // 如果是"AI"部分 | |
| nextDelay = 20; // 固定20毫秒的极快速度 | |
| } | |
| // 递归调用下一个字符 | |
| setTimeout(() => showNextCharacter(index + 1), nextDelay); | |
| } else { | |
| // 输入完成,设置显示状态 | |
| currentIndex = text.length; | |
| typingCompleted = true; | |
| textAnimState = DISPLAY; | |
| stateTimer = millis(); | |
| // 最大化霓虹辉光效果 | |
| neonGlow.style.opacity = '1'; | |
| } | |
| } | |
| function deleteCharacters() { | |
| // 反向逐个设置动画 | |
| for (let i = charSpans.length - 1; i >= 0; i--) { | |
| const charSpan = charSpans[i]; | |
| const delay = (charSpans.length - 1 - i) * 40; // 40ms的删除间隔,加快删除速度 | |
| setTimeout(() => { | |
| charSpan.style.animation = 'characterExit 0.25s forwards'; | |
| // 更新霓虹辉光效果 | |
| neonGlow.style.opacity = i / text.length; | |
| // 当最后一个字符动画开始后,等待动画完成再清空 | |
| if (i === 0) { | |
| setTimeout(() => { | |
| // 清空所有字符 | |
| typewriterElement.innerHTML = ''; | |
| charSpans = []; | |
| // 所有字符已删除,设置等待状态 | |
| textAnimState = WAITING; | |
| stateTimer = millis(); | |
| currentIndex = 0; | |
| }, 300); // 等待最后一个字符退出动画完成 | |
| } | |
| }, delay); | |
| } | |
| } | |
| function checkTextAnimationState() { | |
| const currentTime = millis(); | |
| // 添加一个小的性能优化,通过分散状态检查避免同步处理过多 | |
| if (frameCount % 2 !== 0) return; | |
| // 状态转换逻辑 - 保持动画流畅 | |
| if (textAnimState === DISPLAY && currentTime - stateTimer > displayTime) { | |
| textAnimState = DELETING; | |
| deleteCharacters(); | |
| } else if (textAnimState === WAITING && currentTime - stateTimer > waitingTime) { | |
| textAnimState = TYPING; | |
| startTypewriter(); | |
| } | |
| } | |
| function draw() { | |
| // 保持稳定的帧率 | |
| const currentTime = performance.now(); | |
| if (currentTime - lastFrameTime < frameInterval) { | |
| return; // 跳过这一帧以维持稳定帧率 | |
| } | |
| lastFrameTime = currentTime; | |
| // 深色背景,降低透明度以创建拖尾效果 | |
| background(8, 15, 45, 15); // 更深暗的背景 | |
| // 始终保持高帧率动画,无论文字状态如何 | |
| // 检查并更新文本动画状态 | |
| if (typingStarted) { | |
| checkTextAnimationState(); | |
| } | |
| // 更新焦点位置 | |
| updateFocalPoints(); | |
| // 更新并显示所有粒子 | |
| for (let particle of particles) { | |
| particle.update(); | |
| particle.display(); | |
| } | |
| // 绘制连线 | |
| drawConnections(); | |
| // 更新并显示文字围绕粒子 | |
| if (typingStarted) { | |
| for (let particle of textParticles) { | |
| particle.update(); | |
| particle.display(); | |
| } | |
| } | |
| } | |
| // 文字围绕粒子类 | |
| class TextParticle { | |
| constructor() { | |
| this.reset(); | |
| // 初始角度随机分布 | |
| this.angle = random(TWO_PI); | |
| this.distance = random(120, 300); | |
| } | |
| reset() { | |
| // 设置粒子参数 | |
| this.size = random(3, 7); | |
| this.speed = random(0.01, 0.04); // 增加速度 | |
| this.oscillationSpeed = random(0.03, 0.08); // 增加波动速度 | |
| this.oscillationAmplitude = random(30, 70); // 增加波动幅度 | |
| this.updateColor(); | |
| this.trail = []; | |
| this.maxTrailLength = 6; // 增加轨迹长度 | |
| } | |
| updateColor() { | |
| // 根据当前配色方案更新颜色 | |
| const scheme = colorSchemes[activeColorScheme]; | |
| const colorIndex = floor(random(scheme.length)); | |
| const baseColor = scheme[colorIndex]; | |
| // 添加一些随机变化 | |
| this.color = color( | |
| baseColor[0] + random(-20, 20), | |
| baseColor[1] + random(-20, 20), | |
| baseColor[2] + random(-20, 20), | |
| baseColor[3] | |
| ); | |
| } | |
| update() { | |
| // 围绕屏幕中心旋转 | |
| const centerX = width / 2; | |
| const centerY = height / 2; | |
| // 更新角度 | |
| this.angle += this.speed; | |
| // 计算当前距离(添加波动) | |
| const currentDistance = this.distance + sin(frameCount * this.oscillationSpeed) * this.oscillationAmplitude; | |
| // 计算新位置 | |
| const x = centerX + cos(this.angle) * currentDistance; | |
| const y = centerY + sin(this.angle) * currentDistance; | |
| // 保存位置到轨迹 | |
| this.trail.unshift({x, y}); | |
| if (this.trail.length > this.maxTrailLength) { | |
| this.trail.pop(); | |
| } | |
| // 随机更新颜色 | |
| if (random() < 0.003) { | |
| this.updateColor(); | |
| } | |
| } | |
| display() { | |
| // 绘制轨迹 | |
| for (let i = 0; i < this.trail.length; i++) { | |
| const p = this.trail[i]; | |
| const alpha = map(i, 0, this.trail.length - 1, 1, 0); | |
| const size = map(i, 0, this.trail.length - 1, this.size, 0); | |
| fill(red(this.color), green(this.color), blue(this.color), alpha * 255); | |
| noStroke(); | |
| ellipse(p.x, p.y, size, size); | |
| } | |
| } | |
| } | |
| function updateFocalPoints() { | |
| for (let i = focalPoints.length - 1; i >= 0; i--) { | |
| let point = focalPoints[i]; | |
| // 减少寿命 | |
| point.life--; | |
| // 移动焦点 | |
| point.pos.add(point.vel); | |
| // 边界检查,碰到边缘反弹 | |
| if (point.pos.x < 100 || point.pos.x > width - 100) point.vel.x *= -1; | |
| if (point.pos.y < 100 || point.pos.y > height - 100) point.vel.y *= -1; | |
| // 如果寿命结束,移除焦点 | |
| if (point.life <= 0) { | |
| focalPoints.splice(i, 1); | |
| // 添加新的焦点保持平衡 | |
| if (focalPoints.length < 5) { | |
| focalPoints.push({ | |
| pos: createVector(random(width), random(height)), | |
| vel: createVector(random(-0.3, 0.3), random(-0.3, 0.3)), | |
| life: random(300, 600) | |
| }); | |
| } | |
| } | |
| } | |
| } | |
| // 粒子类 | |
| class Particle { | |
| constructor() { | |
| // 随机初始位置 | |
| this.pos = createVector(random(width), random(height)); | |
| // 随机速度 | |
| this.vel = createVector(random(-0.5, 0.5), random(-0.5, 0.5)); | |
| // 粒子大小,增加变化范围 | |
| this.size = random(2, 9); | |
| // 更新颜色 | |
| this.updateColor(); | |
| // 添加透明度变化 | |
| this.alpha = random(150, 230); | |
| // 添加轻微的抖动效果 | |
| this.offset = random(0, 1000); | |
| // 粒子寿命 | |
| this.maxLife = random(300, 700); | |
| this.life = this.maxLife; | |
| } | |
| updateColor() { | |
| // 根据当前配色方案随机选择颜色 | |
| const scheme = colorSchemes[activeColorScheme]; | |
| this.colorType = floor(random(3)); | |
| this.baseColor = scheme[this.colorType]; | |
| } | |
| update() { | |
| // 降低生命值 | |
| this.life--; | |
| if (this.life <= 0) { | |
| // 重置粒子而不是创建新粒子,保持数量不变 | |
| this.pos = createVector(random(width), random(height)); | |
| this.vel = createVector(random(-0.5, 0.5), random(-0.5, 0.5)); | |
| this.life = this.maxLife; | |
| this.updateColor(); | |
| } | |
| // 如果打字效果开始,增加更多对中心的吸引力 | |
| if (typingStarted) { | |
| // 增加对屏幕中心的轻微吸引力 | |
| let center = createVector(width/2, height/2); | |
| let dirToCenter = p5.Vector.sub(center, this.pos); | |
| let distToCenter = dirToCenter.mag(); | |
| dirToCenter.normalize(); | |
| let centerAttraction = 0.01; | |
| // 如果文字显示完成,增强中心吸引力 | |
| if (typingCompleted) { | |
| centerAttraction = 0.03; | |
| } | |
| if (distToCenter < 500) { // 增加影响范围 | |
| let centerForce = map(distToCenter, 0, 500, centerAttraction, 0.005); | |
| dirToCenter.mult(centerForce); | |
| this.vel.add(dirToCenter); | |
| } | |
| } | |
| // 受到最近焦点的影响 | |
| let nearestPoint = null; | |
| let minDist = Infinity; | |
| for (let point of focalPoints) { | |
| let d = dist(this.pos.x, this.pos.y, point.pos.x, point.pos.y); | |
| if (d < minDist) { | |
| minDist = d; | |
| nearestPoint = point; | |
| } | |
| } | |
| if (nearestPoint) { | |
| let dir = p5.Vector.sub(nearestPoint.pos, this.pos); | |
| let distance = dir.mag(); | |
| dir.normalize(); | |
| // 基于距离的吸引力 | |
| if (distance < 400) { // 增加影响范围 | |
| let force = map(distance, 0, 400, 0.06, 0.01); | |
| dir.mult(force); | |
| this.vel.add(dir); | |
| } | |
| } | |
| // 向鼠标方向移动 - 更敏感的鼠标反应 | |
| let mouse = createVector(mouseX, mouseY); | |
| let dir = p5.Vector.sub(mouse, this.pos); | |
| let distance = dir.mag(); | |
| dir.normalize(); | |
| // 基于距离的吸引力 | |
| if (distance < 300) { // 增加鼠标影响范围 | |
| let mouseForce = map(distance, 0, 300, 0.05, 0.01); | |
| dir.mult(mouseForce); | |
| this.vel.add(dir); | |
| } | |
| // 添加微小随机性和抖动 | |
| this.vel.add(createVector(random(-0.04, 0.04), random(-0.04, 0.04))); | |
| let noiseX = noise(this.offset + frameCount * 0.01) * 0.2 - 0.1; | |
| let noiseY = noise(this.offset + 500 + frameCount * 0.01) * 0.2 - 0.1; | |
| this.vel.add(createVector(noiseX, noiseY)); | |
| // 限制速度 | |
| this.vel.limit(2.2); // 增加最大速度 | |
| // 更新位置 | |
| this.pos.add(this.vel); | |
| // 边界检查 - 环绕屏幕 | |
| if (this.pos.x < 0) this.pos.x = width; | |
| if (this.pos.x > width) this.pos.x = 0; | |
| if (this.pos.y < 0) this.pos.y = height; | |
| if (this.pos.y > height) this.pos.y = 0; | |
| // 轻微变化透明度 | |
| this.alpha = this.alpha + sin(frameCount * 0.05 + this.offset) * 3; | |
| this.alpha = constrain(this.alpha, 150, 230); | |
| // 随机更新颜色,低概率 | |
| if (random() < 0.001) { | |
| this.updateColor(); | |
| } | |
| } | |
| display() { | |
| // 根据颜色类型显示不同颜色 | |
| noStroke(); | |
| // 基于生命周期的不透明度 | |
| let lifeAlpha = map(this.life, 0, this.maxLife, 0, this.alpha); | |
| // 如果打字完成,增强粒子效果 | |
| if (typingCompleted && textAnimState === DISPLAY) { | |
| // 增加发光效果 | |
| drawingContext.shadowBlur = 5; | |
| drawingContext.shadowColor = `rgba(${this.baseColor[0]}, ${this.baseColor[1]}, ${this.baseColor[2]}, 0.5)`; | |
| } | |
| // 应用当前配色方案的颜色 | |
| fill(this.baseColor[0], this.baseColor[1], this.baseColor[2], lifeAlpha); | |
| ellipse(this.pos.x, this.pos.y, this.size); | |
| // 重置阴影效果 | |
| drawingContext.shadowBlur = 0; | |
| } | |
| } | |
| function drawConnections() { | |
| // 绘制高品质的连线 | |
| for (let i = 0; i < particles.length; i++) { | |
| for (let j = i + 1; j < particles.length; j++) { | |
| let p1 = particles[i]; | |
| let p2 = particles[j]; | |
| let d = dist(p1.pos.x, p1.pos.y, p2.pos.x, p2.pos.y); | |
| if (d < 150) { // 增加连线距离 | |
| let alpha = map(d, 0, 150, 120, 0); | |
| // 根据粒子颜色类型决定连线颜色 - 两粒子平均颜色 | |
| const c1 = p1.baseColor; | |
| const c2 = p2.baseColor; | |
| const r = (c1[0] + c2[0]) / 2; | |
| const g = (c1[1] + c2[1]) / 2; | |
| const b = (c1[2] + c2[2]) / 2; | |
| stroke(r, g, b, alpha); | |
| // 如果打字完成,增强连线效果 | |
| if (typingCompleted && textAnimState === DISPLAY) { | |
| // 距离越近线越粗 | |
| let weight = map(d, 0, 150, 1.6, 0.6); | |
| strokeWeight(weight); | |
| // 添加发光效果 | |
| drawingContext.shadowBlur = 3; | |
| drawingContext.shadowColor = `rgba(${r}, ${g}, ${b}, 0.3)`; | |
| } else { | |
| // 距离越近线越粗 | |
| let weight = map(d, 0, 150, 1.4, 0.5); | |
| strokeWeight(weight); | |
| } | |
| line(p1.pos.x, p1.pos.y, p2.pos.x, p2.pos.y); | |
| // 重置阴影效果 | |
| drawingContext.shadowBlur = 0; | |
| } | |
| } | |
| } | |
| } | |
| // 窗口大小调整时重新设置画布大小 | |
| function windowResized() { | |
| resizeCanvas(windowWidth, windowHeight); | |
| } | |
| // 点击时添加优雅的爆发效果 | |
| function mousePressed() { | |
| // 创建新的焦点 | |
| focalPoints.push({ | |
| pos: createVector(mouseX, mouseY), | |
| vel: createVector(random(-0.4, 0.4), random(-0.4, 0.4)), | |
| life: random(250, 500) | |
| }); | |
| // 限制焦点数量 | |
| if (focalPoints.length > 8) { | |
| focalPoints.shift(); | |
| } | |
| // 添加一些新粒子 | |
| for (let i = 0; i < 15; i++) { | |
| let p = new Particle(); | |
| p.pos = createVector(mouseX, mouseY); | |
| p.vel = p5.Vector.random2D().mult(random(2, 4)); | |
| particles.push(p); | |
| } | |
| // 保持粒子数量在合理范围内 | |
| if (particles.length > particleCount + 40) { | |
| particles = particles.slice(0, particleCount + 40); | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment