AI Summary
正在生成摘要...

这是一个集成了雪花飘落、烟花绽放(自动+交互)的网页特效插件。
主要功能
- 自动烟花:页面加载后自动播放,具有慢速扩散和悬浮感。
- 鼠标/触摸交互:
- 短按:释放小烟花。
- 长按蓄力:出现光圈,蓄力越久烟花越大。
- 雪花背景:全屏雪花飘落。
- 滚动淡出:当用户向下滚动网页时,烟花特效会自动变透明,避免遮挡正文内容(雪花保持常驻)。
- 日期锁定:默认设定了
2025-12-30后自动开启,在此之前默认隐藏(可通过左下角隐形开关手动开启)。 - 功能开关:代码顶部有配置区,可独立关闭雪花、自动烟花或点击交互。
如何使用
- 直接运行:下载
.html文件,用浏览器打开即可。 - 集成到网站:
- 将
<style>标签内的代码放入 CSS 配置。 - 将
<body>内的 HTML 元素放入网页底部。 - 将
<script>标签内的代码放入 JS 配置。
- 将
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>盛世烟火 · 国风雪夜</title>
<!-- ========================================= -->
<!-- 1. CSS 样式区 -->
<!-- ========================================= -->
<style>
/* 基础页面样式 (仅为了演示,集成时可删除) */
body {
margin: 0;
padding: 0;
background-color: #f4f4f4; /* 模拟网页背景 */
height: 200vh; /* 模拟长页面以便测试滚动 */
}
/* --------------------------------------- */
/* 以下为特效核心 CSS */
/* --------------------------------------- */
/* 烟花前景画布 */
#fireworks-canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 99999;
pointer-events: none;
transition: opacity 1s ease;
}
/* 底部文字 */
.fw-title-container {
position: fixed;
bottom: 15px;
left: 0;
width: 100%;
text-align: center;
z-index: 100000;
pointer-events: none;
font-family: "KaiTi", "STKaiti", "SimKai", serif;
display: flex;
justify-content: center;
align-items: baseline;
gap: 15px;
text-shadow: 1px 1px 2px rgba(0,0,0,0.2);
transition: opacity 1s ease;
}
.fw-title-container h1 {
margin: 0;
font-size: 24px;
color: #d43838;
font-weight: bold;
}
.fw-title-container p {
margin: 0;
font-size: 14px;
color: #666;
}
/* 移动端适配 */
@media (max-width: 768px) {
.fw-title-container h1 { font-size: 1.4rem; }
.fw-title-container p { font-size: 0.9rem; }
}
/* 左下角隐形开关 */
#fw-toggle-btn {
position: fixed;
bottom: 10px;
left: 10px;
width: 30px;
height: 30px;
border-radius: 50%;
background: rgba(0,0,0,0.2);
cursor: pointer;
z-index: 100001;
opacity: 0.1; /* PC端默认隐藏 */
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: transparent;
user-select: none;
-webkit-tap-highlight-color: transparent; /* 移除移动端点击高亮 */
}
/* 仅在支持鼠标悬停的设备(电脑)上启用 Hover 效果 */
@media (hover: hover) {
#fw-toggle-btn:hover {
opacity: 1;
background: rgba(212, 56, 56, 0.8);
color: white;
box-shadow: 0 0 10px rgba(212, 56, 56, 0.5);
}
}
/* 移动端(无悬停)样式调整 */
@media (hover: none) {
#fw-toggle-btn {
opacity: 0.3; /* 移动端默认稍微可见,方便寻找 */
color: rgba(255,255,255,0.5); /* 图标稍微可见 */
}
/* 点击时的反馈 */
#fw-toggle-btn:active {
opacity: 1;
background: rgba(212, 56, 56, 0.8);
}
}
.fw-hidden {
opacity: 0 !important;
pointer-events: none !important;
}
/* 鼠标蓄力光圈样式 */
#charge-circle {
position: fixed;
border-radius: 50%;
border: 2px solid rgba(255, 255, 255, 0.9); /* 稍微调亮描边 */
background: rgba(255, 255, 255, 0.15);
pointer-events: none;
opacity: 0;
z-index: 99999;
transform-origin: center center;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.3); /* 加一点发光效果 */
}
</style>
</head>
<body>
<!-- ========================================= -->
<!-- 2. HTML 结构区 -->
<!-- ========================================= -->
<!-- 烟花画布 -->
<canvas id="fireworks-canvas"></canvas>
<!-- 底部文字 -->
<div class="fw-title-container">
<!-- 可选标题 -->
<!-- <h1>新年快乐</h1> -->
<p>新年快乐 |</p>
<p>岁岁常欢愉 · 万事皆胜意</p>
<p>| 吉庆有余</p>
<!-- 可选标题 -->
<!-- <h1>吉庆有余</h1> -->
</div>
<!-- 左下角开关 -->
<div id="fw-toggle-btn" title="切换特效">🎆</div>
<!-- 鼠标蓄力光圈 -->
<div id="charge-circle"></div>
<!-- ========================================= -->
<!-- 3. JavaScript 逻辑区 -->
<!-- ========================================= -->
<script>
(function() {
// ==========================================================
// 【参数配置区】
// ==========================================================
const config = {
// --- 8. 功能开关 ---
enableSnow: true, // 是否启用雪花(true/false)
enableAutoFireworks: true, // 是否启用自动烟花(true/false)
enableClickFireworks: true, // 是否启用鼠标点击/蓄力烟花(true/false)
// 1. 自动发射烟花的大小控制
largeFireworkChance: 0.3,
// 2. 烟花扩散速度
baseSpeedSmall: 1.5,
baseSpeedLarge: 4.5,
// 3. 物理环境
friction: 0.99,
gravity: 0.005,
// 4. 存活时间
decayMin: 0.0008,
decayMax: 0.002,
// 5. 粒子视觉
particleLineWidth: 2.5,
// 6. 自动发射频率
autoLaunchMin: 80,
autoLaunchMax: 180,
// 7. 鼠标/触摸蓄力机制
chargeMinSpeed: 1.0,
chargeMaxSpeed: 7.0,
chargeDuration: 1500,
chargeCircleMaxSize: 80
};
// ==========================================================
const canvas = document.getElementById('fireworks-canvas');
const titleContainer = document.querySelector('.fw-title-container');
const toggleBtn = document.getElementById('fw-toggle-btn');
const chargeCircle = document.getElementById('charge-circle');
if (!canvas || !toggleBtn || !chargeCircle) return;
const ctx = canvas.getContext('2d');
let width, height;
// --- 状态控制 --- 此处可以设置定时自动开启 ---
const targetDate = new Date('2025-12-30T00:00:00+08:00').getTime();
const now = Date.now();
let isVisible = now >= targetDate;
function updateVisibility() {
if (isVisible) {
canvas.classList.remove('fw-hidden');
if(titleContainer) titleContainer.classList.remove('fw-hidden');
} else {
canvas.classList.add('fw-hidden');
if(titleContainer) titleContainer.classList.add('fw-hidden');
}
}
updateVisibility();
toggleBtn.addEventListener('click', function() {
isVisible = !isVisible;
updateVisibility();
});
function resize() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
const random = (min, max) => Math.random() * (max - min) + min;
const colors = [
'#FF4500', '#FFD700', '#ADFF2F', '#00BFFF', '#BA55D3',
'#FF6347', '#FF8C00', '#FFFF00', '#00FF7F', '#1E90FF',
'#DA70D6', '#FF1493'
];
// --- 烟花粒子类 ---
class Particle {
constructor(x, y, color, speedScale = 1) {
this.x = x; this.y = y; this.color = color;
const angle = random(0, Math.PI * 2);
const baseSpeed = random(config.baseSpeedSmall, config.baseSpeedLarge);
const speed = baseSpeed * random(0.8, 1.2) * speedScale;
this.vx = Math.cos(angle) * speed;
this.vy = Math.sin(angle) * speed;
this.alpha = 1;
this.friction = config.friction;
this.gravity = config.gravity;
this.decay = random(config.decayMin, config.decayMax);
this.coordinates = [];
this.coordinateCount = 8;
while(this.coordinateCount--) this.coordinates.push([this.x, this.y]);
}
update() {
this.coordinates.pop();
this.coordinates.unshift([this.x, this.y]);
this.vx *= this.friction;
this.vy *= this.friction;
this.vy += this.gravity;
this.x += this.vx;
this.y += this.vy;
this.alpha -= this.decay;
}
draw(scrollOpacity) {
ctx.beginPath();
ctx.moveTo(this.coordinates[this.coordinates.length - 1][0], this.coordinates[this.coordinates.length - 1][1]);
ctx.lineTo(this.x, this.y);
const currentAlpha = this.alpha * scrollOpacity;
if (currentAlpha <= 0) return;
ctx.strokeStyle = this.color;
ctx.globalAlpha = currentAlpha;
ctx.lineWidth = config.particleLineWidth;
ctx.stroke();
ctx.fillStyle = this.color;
ctx.globalAlpha = currentAlpha * 0.7;
ctx.arc(this.x, this.y, config.particleLineWidth * 0.5, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1;
}
}
// --- 雪花类 ---
class Snowflake {
constructor() { this.reset(); }
reset() {
this.x = Math.random() * width;
this.y = Math.random() * -height;
this.vy = 1 + Math.random() * 2;
this.vx = 0.5 - Math.random();
this.r = 1 + Math.random() * 2;
this.o = 0.5 + Math.random() * 0.5;
}
update() {
this.y += this.vy; this.x += this.vx;
if (this.y > height) this.reset();
}
draw() {
ctx.fillStyle = `rgba(255, 255, 255, ${this.o})`;
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
ctx.fill();
}
}
let particles = [], snowflakes = [];
for(let i=0; i<80; i++) snowflakes.push(new Snowflake());
function createParticles(x, y, speedScale = 1, particleCountScale = 1) {
const isLarge = Math.random() < config.largeFireworkChance;
const baseColorIndex = Math.floor(random(0, colors.length));
const color = colors[baseColorIndex];
let count = isLarge ? Math.floor(random(150, 220)) : Math.floor(random(60, 100));
count = Math.floor(count * particleCountScale);
while(count--) {
const pColor = Math.random() < 0.2 ? colors[Math.floor(random(0, colors.length))] : color;
particles.push(new Particle(x, y, pColor, speedScale));
}
}
let timerTick = 0, timerTotal = 80;
// --- 主循环 ---
function loop() {
requestAnimationFrame(loop);
ctx.clearRect(0, 0, width, height);
if (isVisible) {
const scrollY = window.scrollY || document.documentElement.scrollTop;
const fadeStart = window.innerHeight * 0.2;
const fadeEnd = window.innerHeight * 0.9;
let fwOpacity = 1;
if (scrollY > fadeStart) {
fwOpacity = 1 - (scrollY - fadeStart) / (fadeEnd - fadeStart);
}
if (fwOpacity < 0) fwOpacity = 0;
// 1. 绘制雪花 (受开关控制)
if (config.enableSnow) {
snowflakes.forEach(f => { f.update(); f.draw(); });
}
// 2. 绘制烟花
let j = particles.length;
while(j--) {
particles[j].update();
if (fwOpacity > 0) {
particles[j].draw(fwOpacity);
}
if(particles[j].alpha <= 0) particles.splice(j, 1);
}
// 3. 自动发射 (受开关控制)
if (fwOpacity > 0 && config.enableAutoFireworks) {
if(timerTick >= timerTotal) {
createParticles(random(0, width), random(0, height * 0.6), random(0.8, 1.2), random(0.8, 1.2));
timerTick = 0;
timerTotal = random(config.autoLaunchMin, config.autoLaunchMax);
} else { timerTick++; }
}
}
}
// --- 鼠标蓄力逻辑 ---
let mouseDownTime = 0;
let mouseX = 0;
let mouseY = 0;
let chargeAnimFrameId;
function updateChargeAnim() {
if (mouseDownTime > 0 && isVisible) {
const holdDuration = Date.now() - mouseDownTime;
const chargeProgress = Math.min(1, holdDuration / config.chargeDuration);
const circleSize = config.chargeCircleMaxSize * chargeProgress;
chargeCircle.style.width = circleSize + 'px';
chargeCircle.style.height = circleSize + 'px';
chargeCircle.style.left = (mouseX - circleSize / 2) + 'px';
chargeCircle.style.top = (mouseY - circleSize / 2) + 'px';
chargeCircle.style.opacity = '1';
chargeAnimFrameId = requestAnimationFrame(updateChargeAnim);
}
}
window.addEventListener('mousedown', function(e) {
if (isVisible && config.enableClickFireworks) {
if (window.scrollY > window.innerHeight * 0.5) return;
mouseDownTime = Date.now();
mouseX = e.pageX;
mouseY = e.pageY - window.scrollY;
chargeCircle.style.width = '0px';
chargeCircle.style.height = '0px';
chargeCircle.style.left = mouseX + 'px';
chargeCircle.style.top = mouseY + 'px';
chargeCircle.style.opacity = '1';
cancelAnimationFrame(chargeAnimFrameId);
updateChargeAnim();
}
});
window.addEventListener('mousemove', function(e) {
if (mouseDownTime > 0 && isVisible && config.enableClickFireworks) {
mouseX = e.pageX;
mouseY = e.pageY - window.scrollY;
}
});
window.addEventListener('mouseup', function(e) {
if (isVisible && mouseDownTime > 0 && config.enableClickFireworks) {
cancelAnimationFrame(chargeAnimFrameId);
const holdDuration = Date.now() - mouseDownTime;
const chargeProgress = Math.min(1, holdDuration / config.chargeDuration);
const speedScale = config.chargeMinSpeed + (config.chargeMaxSpeed - config.chargeMinSpeed) * chargeProgress;
const particleCountScale = 0.5 + (2.0 - 0.5) * chargeProgress;
createParticles(mouseX, mouseY, speedScale, particleCountScale);
mouseDownTime = 0;
chargeCircle.style.opacity = '0';
setTimeout(() => {
if (mouseDownTime === 0) {
chargeCircle.style.width = '0px';
chargeCircle.style.height = '0px';
}
}, 100);
}
});
// --- 触摸逻辑 (已修复滚动冲突) ---
let touchStartX = 0;
let touchStartY = 0;
let isScrolling = false;
window.addEventListener('touchstart', function(e) {
if (isVisible && e.touches.length === 1 && config.enableClickFireworks) {
if (window.scrollY > window.innerHeight * 0.5) return;
// 注意:不调用 preventDefault,允许浏览器处理滚动
isScrolling = false;
touchStartX = e.touches[0].pageX;
touchStartY = e.touches[0].pageY;
mouseDownTime = Date.now();
mouseX = e.touches[0].pageX;
mouseY = e.touches[0].pageY - window.scrollY;
chargeCircle.style.width = '0px';
chargeCircle.style.height = '0px';
chargeCircle.style.left = mouseX + 'px';
chargeCircle.style.top = mouseY + 'px';
chargeCircle.style.opacity = '1';
cancelAnimationFrame(chargeAnimFrameId);
updateChargeAnim();
}
}, { passive: true }); // 开启 passive
window.addEventListener('touchmove', function(e) {
if (mouseDownTime > 0 && isVisible && e.touches.length === 1 && config.enableClickFireworks) {
let currentX = e.touches[0].pageX;
let currentY = e.touches[0].pageY;
let diffX = Math.abs(currentX - touchStartX);
let diffY = Math.abs(currentY - touchStartY);
// 如果发生了明显的移动,判定为滚动,取消蓄力
if (diffX > 10 || diffY > 10) {
isScrolling = true;
mouseDownTime = 0;
chargeCircle.style.opacity = '0';
chargeCircle.style.width = '0px';
chargeCircle.style.height = '0px';
cancelAnimationFrame(chargeAnimFrameId);
return;
}
mouseX = currentX;
mouseY = currentY - window.scrollY;
}
}, { passive: true });
window.addEventListener('touchend', function(e) {
if (isVisible && mouseDownTime > 0 && !isScrolling && config.enableClickFireworks) {
cancelAnimationFrame(chargeAnimFrameId);
const holdDuration = Date.now() - mouseDownTime;
const chargeProgress = Math.min(1, holdDuration / config.chargeDuration);
const speedScale = config.chargeMinSpeed + (config.chargeMaxSpeed - config.chargeMinSpeed) * chargeProgress;
const particleCountScale = 0.5 + (2.0 - 0.5) * chargeProgress;
createParticles(mouseX, mouseY, speedScale, particleCountScale);
mouseDownTime = 0;
chargeCircle.style.opacity = '0';
setTimeout(() => {
if (mouseDownTime === 0) {
chargeCircle.style.width = '0px';
chargeCircle.style.height = '0px';
}
}, 100);
}
});
loop();
})();
</script>
</body>
</html>
新年快乐,吉庆有余!
参与讨论