子涵的小助手正在绞尽脑汁···
这里是摘要QwQ
LongCat-2.0

在 Hexo Butterfly 主题中添加一个完全透明的 Canvas 下雪特效,雪花带有柔和光晕,能避开鼠标,且数量根据屏幕大小自适应。


一、效果特点

  • 完全透明背景:不影响博客内容,仅显示雪花
  • 无拖尾:雪花独立飘落,没有残影
  • 自适应数量:根据屏幕面积自动调整雪花数量(手机 ≈ 45 个,桌面 ≈ 150 个)
  • 鼠标交互:雪花会避开以鼠标为圆心的圆形区域
  • 光晕效果:雪花带有柔和的外发光,视觉更立体
  • 纯色雪花:默认白色,可自定义颜色

二、完整代码(snow.js

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
// snow.js - 透明背景无拖尾光晕雪花(自适应屏幕)
(function() {
// 创建画布(透明背景)
const canvas = document.createElement('canvas');
canvas.id = 'snowCanvas';
canvas.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999;';
document.body.appendChild(canvas);

const ctx = canvas.getContext('2d');
let width, height;
let mouseX = -9999, mouseY = -9999;

// ----- 自适应配置 -----
// 基准:1920x1080 桌面下雪花数量为 150
const BASE_COUNT = 150;
const BASE_WIDTH = 1920;
const BASE_HEIGHT = 1080;
const BASE_AREA = BASE_WIDTH * BASE_HEIGHT;

// 雪花配置(部分参数也可以根据屏幕微调)
const CONFIG = {
minSize: 3,
maxSize: 10,
speed: 0.5,
wind: 0.3,
color: 'rgba(255, 255, 255, 0.9)',
glowColor: 'rgba(180, 210, 255, 0.3)',
glowBlur: 15,
mouseRadius: 100,
};

// ----- 根据屏幕面积计算雪花数量 -----
function getSnowflakeCount() {
const area = width * height;
let count = Math.round((area / BASE_AREA) * BASE_COUNT);
return Math.min(200, Math.max(30, count));
}

let snowflakes = [];

function resizeCanvas() {
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width;
canvas.height = height;

const newCount = getSnowflakeCount();
while (snowflakes.length < newCount) {
snowflakes.push(createSnowflake());
}
while (snowflakes.length > newCount) {
const idx = Math.floor(Math.random() * snowflakes.length);
snowflakes.splice(idx, 1);
}
}
window.addEventListener('resize', resizeCanvas);

// 鼠标跟踪
document.addEventListener('mousemove', (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
});
document.addEventListener('mouseleave', () => {
mouseX = -9999;
mouseY = -9999;
});

function createSnowflake() {
return {
x: Math.random() * width,
y: Math.random() * height - height,
radius: CONFIG.minSize + Math.random() * (CONFIG.maxSize - CONFIG.minSize),
speed: CONFIG.speed + Math.random() * 0.6,
opacity: 0.5 + Math.random() * 0.5,
phase: Math.random() * Math.PI * 2,
};
}

resizeCanvas();

function drawSnowflake(s) {
// 鼠标排斥
let dx = 0, dy = 0;
const distX = s.x - mouseX;
const distY = s.y - mouseY;
const dist = Math.sqrt(distX * distX + distY * distY);
if (dist < CONFIG.mouseRadius && dist > 0) {
const angle = Math.atan2(distY, distX);
const force = (CONFIG.mouseRadius - dist) / CONFIG.mouseRadius * 8;
dx = Math.cos(angle) * force;
dy = Math.sin(angle) * force;
}

// 更新位置
s.x += Math.sin(s.phase + Date.now() * 0.001) * CONFIG.wind + dx * 0.1;
s.y += s.speed * 0.8 + dy * 0.1;
s.phase += 0.01;

if (s.y > height + 50) {
Object.assign(s, createSnowflake());
s.y = -20;
s.x = Math.random() * width;
}
if (s.x < -50) s.x = width + 50;
if (s.x > width + 50) s.x = -50;

// 绘制(光晕 + 核心)
ctx.save();
ctx.shadowColor = CONFIG.glowColor;
ctx.shadowBlur = CONFIG.glowBlur * (s.radius / 8);
ctx.globalAlpha = s.opacity * 0.85;
ctx.fillStyle = CONFIG.color;
ctx.beginPath();
ctx.arc(s.x, s.y, s.radius, 0, Math.PI * 2);
ctx.fill();

ctx.shadowBlur = 0;
ctx.globalAlpha = s.opacity * 0.4;
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
ctx.beginPath();
ctx.arc(s.x, s.y, s.radius * 0.3, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}

function animate() {
ctx.clearRect(0, 0, width, height);
for (const s of snowflakes) {
drawSnowflake(s);
}
requestAnimationFrame(animate);
}

animate();
})();

三、集成到 Hexo Butterfly 主题

保存文件:将上述代码保存为 /source/js/snow.js。

注入配置:在 _config.butterfly.yml 的 inject.bottom 中添加:

1
2
3
inject:
bottom:
- <script src="/js/snow.js"></script>