Skip to content

Instantly share code, notes, and snippets.

@nicekate
Created March 7, 2025 13:50
Show Gist options
  • Save nicekate/89d76b3aecfded678edf396488d323ff to your computer and use it in GitHub Desktop.
Save nicekate/89d76b3aecfded678edf396488d323ff to your computer and use it in GitHub Desktop.
<!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