乔乔生日快乐
<html>
<head>
<meta charset="UTF-8" />
<title>乔乔生日快乐</title>
<style type="text/css">
body,
html {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: #000000;
}
canvas {
position: absolute;
top: 0;
left: 0;
}
#text-canvas {
z-index: 1;
}
#fireworks-canvas {
z-index: 2;
}
#cake-canvas {
z-index: 3;
}
#audio-control {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 100;
background: rgba(255, 255, 255, 0.2);
border-radius: 50%;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: white;
font-size: 20px;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<canvas id="text-canvas"></canvas>
<canvas id="fireworks-canvas"></canvas>
<canvas id="cake-canvas"></canvas>
<script type="text/javascript">
// 生日快乐歌的音符序列
const birthdaySong = [
{ note: 'G4', duration: 0.5 }, { note: 'G4', duration: 0.5 },
{ note: 'A4', duration: 1 }, { note: 'G4', duration: 1 },
{ note: 'C5', duration: 1 }, { note: 'B4', duration: 2 },
{ note: 'G4', duration: 0.5 }, { note: 'G4', duration: 0.5 },
{ note: 'A4', duration: 1 }, { note: 'G4', duration: 1 },
{ note: 'D5', duration: 1 }, { note: 'C5', duration: 2 },
{ note: 'G4', duration: 0.5 }, { note: 'G4', duration: 0.5 },
{ note: 'G5', duration: 1 }, { note: 'E5', duration: 1 },
{ note: 'C5', duration: 1 }, { note: 'B4', duration: 1 },
{ note: 'A4', duration: 2 },
{ note: 'F5', duration: 0.5 }, { note: 'F5', duration: 0.5 },
{ note: 'E5', duration: 1 }, { note: 'C5', duration: 1 },
{ note: 'D5', duration: 1 }, { note: 'C5', duration: 2 }
];
// 音符频率映射
const noteFrequencies = {
'C4': 261.63, 'D4': 293.66, 'E4': 329.63, 'F4': 349.23,
'G4': 392.00, 'A4': 440.00, 'B4': 493.88, 'C5': 523.25,
'D5': 587.33, 'E5': 659.25, 'F5': 698.46, 'G5': 783.99
};
// 创建音频上下文
let audioContext;
let playPrompt = document.getElementById('playPrompt');
// 播放音符函数
function playNote(frequency, duration) {
return new Promise((resolve) => {
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.type = 'sine';
oscillator.frequency.value = frequency;
// 设置音量包络
gainNode.gain.setValueAtTime(0, audioContext.currentTime);
gainNode.gain.linearRampToValueAtTime(0.3, audioContext.currentTime + 0.1);
gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + duration * 0.9);
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.start();
oscillator.stop(audioContext.currentTime + duration);
oscillator.onended = () => resolve();
});
}
// 播放整首歌
async function playSong() {
const tempo = 100; // 每分钟100拍
const beatDuration = 60 / tempo;
while (true) { // 无限循环播放
for (const {note, duration} of birthdaySong) {
const frequency = noteFrequencies[note];
await playNote(frequency, duration * beatDuration);
}
await new Promise(resolve => setTimeout(resolve, 2000)); // 歌曲间隔2秒
}
}
// 尝试自动播放
function tryAutoPlay() {
try {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
playSong().catch(e => {
console.log('自动播放被阻止:', e);
showPlayPrompt();
});
} catch (e) {
console.log('创建音频上下文失败:', e);
showPlayPrompt();
}
}
// 显示播放提示
function showPlayPrompt() {
playPrompt.style.display = 'block';
document.body.addEventListener('click', function startOnClick() {
audioContext.resume().then(() => {
playSong();
playPrompt.style.display = 'none';
});
document.body.removeEventListener('click', startOnClick);
});
}
// 页面加载后尝试自动播放
window.addEventListener('load', tryAutoPlay);
// 文字画布
const textCanvas = document.getElementById("text-canvas");
const textCtx = textCanvas.getContext("2d");
// 烟花画布
const fireworksCanvas = document.getElementById("fireworks-canvas");
// 蛋糕画布
const cakeCanvas = document.getElementById("cake-canvas");
const cakeCtx = cakeCanvas.getContext("2d");
// 初始化画布大小
function resize() {
textCanvas.width = fireworksCanvas.width = cakeCanvas.width = window.innerWidth;
textCanvas.height = fireworksCanvas.height = cakeCanvas.height = window.innerHeight;
}
window.onresize = resize;
resize();
// 清空画布
function clearCanvas(ctx) {
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
// 烟花系统
const Fireworks = function () {
const self = this;
const rand = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
self.fireworkSettings = {
autoFireDelay: 400,
particleCount: 250,
fireworkSpeed: 5,
hueRange: 360,
autoFireEnabled: true
};
self.init = function () {
self.ctx = fireworksCanvas.getContext("2d");
self.ctx.lineCap = "round";
self.ctx.lineJoin = "round";
self.particles = [];
self.fireworks = [];
self.currentHue = rand(0, 360);
self.lineWidth = 1;
self.bindEvents();
self.loop();
if (self.fireworkSettings.autoFireEnabled) {
self.autoFire();
}
};
self.autoFire = function () {
setInterval(() => {
self.currentHue = rand(0, self.fireworkSettings.hueRange);
self.createFireworks(
rand(100, window.innerWidth - 100),
window.innerHeight,
rand(100, window.innerWidth - 100),
rand(50, window.innerHeight * 0.7)
);
}, self.fireworkSettings.autoFireDelay);
};
self.createFireworks = function (startX, startY, targetX, targetY) {
const newFirework = {
x: startX,
y: startY,
startX,
startY,
targetX,
targetY,
speed: 3 + Math.random() * self.fireworkSettings.fireworkSpeed,
angle: Math.atan2(targetY - startY, targetX - startX),
hue: self.currentHue,
brightness: rand(60, 90),
alpha: 0.7 + Math.random() * 0.3
};
self.fireworks.push(newFirework);
};
self.createParticles = function (x, y, hue) {
for (let i = 0; i < self.fireworkSettings.particleCount; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 2 + Math.random() * 6;
self.particles.push({
x,
y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
hue: hue + rand(-30, 30),
brightness: rand(60, 90),
alpha: 0.6 + Math.random() * 0.4,
decay: 0.01 + Math.random() * 0.02,
size: 1 + Math.random() * 3
});
}
};
self.updateFireworks = function () {
for (let i = self.fireworks.length - 1; i >= 0; i--) {
const f = self.fireworks[i];
const dx = f.targetX - f.x;
const dy = f.targetY - f.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 10) {
self.createParticles(f.x, f.y, f.hue);
self.fireworks.splice(i, 1);
} else {
f.x += Math.cos(f.angle) * f.speed;
f.y += Math.sin(f.angle) * f.speed;
}
}
};
self.updateParticles = function () {
for (let i = self.particles.length - 1; i >= 0; i--) {
const p = self.particles[i];
p.x += p.vx;
p.y += p.vy;
p.vy += 0.05;
p.alpha -= p.decay;
if (p.alpha <= 0) {
self.particles.splice(i, 1);
}
}
};
self.drawFireworks = function () {
self.ctx.globalCompositeOperation = "lighter";
for (const f of self.fireworks) {
self.ctx.beginPath();
self.ctx.moveTo(f.startX, f.startY);
self.ctx.lineTo(f.x, f.y);
self.ctx.strokeStyle = `hsla(${f.hue}, 100%, ${f.brightness}%, ${f.alpha})`;
self.ctx.lineWidth = self.lineWidth;
self.ctx.stroke();
}
};
self.drawParticles = function () {
self.ctx.globalCompositeOperation = "lighter";
for (const p of self.particles) {
self.ctx.beginPath();
self.ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
self.ctx.fillStyle = `hsla(${p.hue}, 100%, ${p.brightness}%, ${p.alpha})`;
self.ctx.fill();
}
};
self.loop = function () {
requestAnimationFrame(self.loop);
self.ctx.globalCompositeOperation = "destination-out";
self.ctx.fillStyle = "rgba(0, 0, 0, 0.15)";
self.ctx.fillRect(0, 0, fireworksCanvas.width, fireworksCanvas.height);
self.updateFireworks();
self.updateParticles();
self.drawFireworks();
self.drawParticles();
};
self.bindEvents = function () {
$(fireworksCanvas).on("click", function (e) {
for (let i = 0; i < 3; i++) {
setTimeout(() => {
self.currentHue = rand(0, self.fireworkSettings.hueRange);
self.createFireworks(
window.innerWidth / 2,
window.innerHeight,
e.pageX - fireworksCanvas.offsetLeft + rand(-50, 50),
e.pageY - fireworksCanvas.offsetTop + rand(-50, 50)
);
}, i * 100);
}
});
};
self.init();
};
// 简约蛋糕设计
const Cake = {
init() {
this.cakeWidth = Math.min(350, window.innerWidth * 0.7);
this.cakeHeight = this.cakeWidth * 0.4;
this.x = (window.innerWidth - this.cakeWidth) / 2;
this.y = window.innerHeight - this.cakeHeight - 80;
// 简约蜡烛设计
this.candles = [];
const candleCount = 5;
const spacing = this.cakeWidth / (candleCount + 1);
for (let i = 0; i < candleCount; i++) {
this.candles.push({
x: this.x + spacing * (i + 1),
y: this.y - 10,
height: 60,
flameSize: 10,
hue: 50 + i * 20, // 暖色调渐变
flicker: Math.random() * Math.PI * 2
});
}
this.drawCake();
},
drawCake() {
cakeCtx.clearRect(0, 0, cakeCanvas.width, cakeCanvas.height);
// 简约蛋糕主体 - 双层设计
cakeCtx.fillStyle = "#f8f1e5"; // 象牙白
cakeCtx.beginPath();
cakeCtx.roundRect(this.x, this.y, this.cakeWidth, this.cakeHeight, 15);
cakeCtx.fill();
// 上层蛋糕
cakeCtx.fillStyle = "#fff8f0"; // 更浅的象牙白
cakeCtx.beginPath();
cakeCtx.roundRect(
this.x + 10,
this.y - 30,
this.cakeWidth - 20,
this.cakeHeight,
10
);
cakeCtx.fill();
// 简约装饰线条
cakeCtx.strokeStyle = "#f2d3ac";
cakeCtx.lineWidth = 3;
cakeCtx.beginPath();
cakeCtx.moveTo(this.x + 20, this.y + 10);
cakeCtx.lineTo(this.x + this.cakeWidth - 20, this.y + 10);
cakeCtx.stroke();
cakeCtx.beginPath();
cakeCtx.moveTo(this.x + 20, this.y + this.cakeHeight - 15);
cakeCtx.lineTo(this.x + this.cakeWidth - 20, this.y + this.cakeHeight - 15);
cakeCtx.stroke();
// 绘制简约蜡烛
this.drawCandles();
requestAnimationFrame(() => this.drawCake());
},
drawCandles() {
const now = Date.now();
this.candles.forEach(candle => {
// 简约蜡烛柱
cakeCtx.fillStyle = `hsl(${candle.hue}, 80%, 85%)`;
cakeCtx.fillRect(candle.x - 6, candle.y - candle.height, 12, candle.height);
// 蜡烛火焰动画
candle.flicker += 0.1;
const flameVariation = Math.sin(candle.flicker) * 0.3 + 0.7;
// 简约火焰效果
const gradient = cakeCtx.createRadialGradient(
candle.x, candle.y - candle.height,
0,
candle.x, candle.y - candle.height,
candle.flameSize * flameVariation
);
gradient.addColorStop(0, `hsla(50, 100%, 90%, 0.9)`);
gradient.addColorStop(1, `hsla(20, 100%, 50%, 0)`);
cakeCtx.fillStyle = gradient;
cakeCtx.beginPath();
cakeCtx.arc(
candle.x,
candle.y - candle.height,
candle.flameSize * flameVariation,
0, Math.PI * 2
);
cakeCtx.fill();
// 简约光晕效果
cakeCtx.globalCompositeOperation = "lighter";
cakeCtx.fillStyle = `hsla(40, 100%, 70%, ${0.2 * flameVariation})`;
cakeCtx.beginPath();
cakeCtx.arc(
candle.x,
candle.y - candle.height,
candle.flameSize * 1.5 * flameVariation,
0, Math.PI * 2
);
cakeCtx.fill();
cakeCtx.globalCompositeOperation = "source-over";
});
}
};
// 闪烁文字系统
class TextParticles {
constructor() {
this.particles = [];
this.colors = ["#f8d3b8", "#f2a6a2", "#d4e6f4", "#f2e8c4", "#efd282"];
this.textAlpha = 1;
this.fadeDirection = -1;
}
createFromText(text) {
clearCanvas(textCtx);
this.particles = [];
// 绘制文字
textCtx.fillStyle = `rgba(255,255,255,${this.textAlpha})`;
textCtx.font = "bold 120px 'Microsoft YaHei', sans-serif";
const texts = text.split("\n");
const lineHeight = 150;
const startY = (textCanvas.height - (texts.length * lineHeight)) / 2;
texts.forEach((line, i) => {
const x = (textCanvas.width - textCtx.measureText(line).width) / 2;
const y = startY + (i * lineHeight);
textCtx.fillText(line, x, y);
});
// 生成粒子
const imageData = textCtx.getImageData(0, 0, textCanvas.width, textCanvas.height).data;
for (let i = 0; i < imageData.length; i += 16) {
if (imageData[i] > 128 && Math.random() > 0.7) {
const x = (i / 4) % textCanvas.width;
const y = Math.floor((i / 4) / textCanvas.width);
this.particles.push({
x,
y,
targetX: x,
targetY: y,
color: this.colors[Math.floor(Math.random() * this.colors.length)],
size: 1 + Math.random() * 2,
alpha: 0.8 + Math.random() * 0.2
});
}
}
clearCanvas(textCtx);
}
updateFade() {
this.textAlpha += this.fadeDirection * 0.02;
if (this.textAlpha <= 0.6 || this.textAlpha >= 1) {
this.fadeDirection *= -1;
}
}
draw() {
this.updateFade();
textCtx.globalCompositeOperation = "lighter";
// 绘制粒子
this.particles.forEach(p => {
textCtx.beginPath();
textCtx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
textCtx.fillStyle = p.color.replace(")", "," + p.alpha + ")").replace("rgb", "rgba");
textCtx.fill();
});
}
}
// 初始化系统
const fireworks = new Fireworks();
const textParticles = new TextParticles();
Cake.init();
// 创建文字粒子
textParticles.createFromText("乔乔\n生日快乐!");
// 动画循环
function animate() {
requestAnimationFrame(animate);
textParticles.draw();
}
animate();
</script>
</body>
</html>