大纲
网站背景根据季节改变,比如春天下雨细雨,夏天太阳很大,秋天落叶,冬天下雨
简述
将整个季节背景切换功能整合成一段独立的JS代码,可以直接在Gmeek的config.json中通过allHeadI用,不需要额外创建多个文件。做法是春天下雨、夏天太阳、秋天落叶、冬天下雪。我们需要提供一个单一的JS文件,它自己判断季节,然后动态创建canvas并绘制相应特效。但注意,夏天太阳效果可能需要不同实现(比如光晕或静态图片),落叶需要复杂的粒子系统。我们可以提供一个精简但有效的实现:使用canvas粒子系统,根据季节改变粒子的外观和行为(春雨用蓝色斜线,夏日用黄色圆形加光晕效果或太阳射线,秋叶用橙色/棕色旋转叶片,冬雪用白色圆形飘落)。这样只需一个JS文件,不侬赖外部库。
考處代码长度,需要确保可读性和完整性。将编写一个自执行的匿名函数,监听DOMContentLoaded,创建全屏canvas,设置z-index为-1(背景),并根据季节启动不同的动画循环。
实现细节:
。获取季节逻辑(月份)。
。定义粒子类。
动画循环根据季节更新粒子属性和绘制。
夏日效果:可以绘制一个发光的太阳,或者简单点:绘制大量闪烁的黄色光点,模拟热浪。为了简洁且视觉明显,可以绘制一个太阳在顶部,并有一些光线射线,同时地面有热浪扭曲(但复杂)。简单做法:绘制一个大的橙色圆形,周围有光线旋转,并且背景是亮黄色渐变。
落叶:使用矩形或椭圆,旋转,缓慢飘落。
春雨:斜线,下落。
冬雪:白色圆形,下落,偶尔有闪烁。
我们将提供完整代码,用户可以复制保存season.js,放入static目录,然后在config.json中通过allHeadI用。
(function() {
// 等待页面加载完成
window.addEventListener('DOMContentLoaded', function() {
// ---------- 季节判断 ----------
function getSeason() {
const month = new Date().getMonth(); // 0-11
if (month >= 2 && month <= 4) return 'spring'; // 3,4,5月
if (month >= 5 && month <= 7) return 'summer'; // 6,7,8月
if (month >= 8 && month <= 10) return 'autumn'; // 9,10,11月
return 'winter'; // 12,1,2月
}
const SEASON = getSeason();
// ---------- Canvas 设置 ----------
const canvas = document.createElement('canvas');
canvas.id = 'season-canvas';
canvas.style.position = 'fixed';
canvas.style.top = '0';
canvas.style.left = '0';
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.zIndex = '-1'; // 置于最底层可改999就在内容前面显示
canvas.style.pointerEvents = 'none'; // 让点击穿透
document.body.appendChild(canvas);
const ctx = canvas.getContext('2d');
let width, height;
let animationId = null;
let particles = [];
// 更新画布尺寸
function resizeCanvas() {
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width;
canvas.height = height;
}
window.addEventListener('resize', () => {
resizeCanvas();
// 重新初始化粒子(避免位置错位)
initParticles();
});
// ---------- 各季节的参数 ----------
let particleCount = 0;
let particleColor = '';
let particleSpeed = 1;
let particleSize = 2;
let customDraw = null; // 自定义绘制函数(用于落叶、太阳等)
// 春天:斜向细雨
function springDraw(particle) {
ctx.beginPath();
ctx.moveTo(particle.x, particle.y);
ctx.lineTo(particle.x - particle.vx * 2, particle.y + particle.vy * 2);
ctx.strokeStyle = particle.color;
ctx.lineWidth = particle.size;
ctx.stroke();
}
function initSpring() {
particleCount = 250;
particleColor = 'rgba(100, 150, 255, 0.6)';
particleSpeed = 5;
particleSize = 1.5;
particles = [];
for (let i = 0; i < particleCount; i++) {
particles.push({
x: Math.random() * width,
y: Math.random() * height,
vx: (Math.random() - 0.5) * 1.5, // 水平飘移
vy: 2 + Math.random() * 4,
size: 1 + Math.random() * 2,
color: `rgba(100, 150, 255, ${0.3 + Math.random() * 0.5})`
});
}
}
// 夏天:烈日 + 热浪光点
let sunAngle = 0;
function summerDraw(particle) {
// 粒子是闪烁的光点
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
ctx.fillStyle = particle.color;
ctx.fill();
}
function drawSun() {
// 绘制太阳(固定左上角或右上角,这里放右上角)
const sunX = width - 80;
const sunY = 80;
// 外光晕
const gradient = ctx.createRadialGradient(sunX, sunY, 10, sunX, sunY, 50);
gradient.addColorStop(0, 'rgba(255, 200, 50, 0.9)');
gradient.addColorStop(1, 'rgba(255, 100, 0, 0)');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(sunX, sunY, 50, 0, Math.PI * 2);
ctx.fill();
// 核心
ctx.fillStyle = '#FFD700';
ctx.beginPath();
ctx.arc(sunX, sunY, 20, 0, Math.PI * 2);
ctx.fill();
// 光线旋转
sunAngle += 0.02;
for (let i = 0; i < 12; i++) {
const angle = sunAngle + (i * Math.PI * 2 / 12);
const dx = Math.cos(angle) * 35;
const dy = Math.sin(angle) * 35;
ctx.beginPath();
ctx.moveTo(sunX + dx * 0.5, sunY + dy * 0.5);
ctx.lineTo(sunX + dx, sunY + dy);
ctx.lineWidth = 4;
ctx.strokeStyle = `rgba(255, 200, 0, ${0.5 + Math.sin(angle * 3) * 0.3})`;
ctx.stroke();
}
}
function initSummer() {
particleCount = 180;
particles = [];
for (let i = 0; i < particleCount; i++) {
particles.push({
x: Math.random() * width,
y: Math.random() * height,
vx: (Math.random() - 0.5) * 0.5,
vy: 0.2 + Math.random() * 0.8,
size: 2 + Math.random() * 4,
color: `rgba(255, 180, 50, ${0.4 + Math.random() * 0.5})`
});
}
}
// 秋天:落叶(旋转的椭圆)
function autumnDraw(particle) {
ctx.save();
ctx.translate(particle.x, particle.y);
ctx.rotate(particle.angle);
ctx.beginPath();
ctx.ellipse(0, 0, particle.sizeX, particle.sizeY, 0, 0, Math.PI * 2);
ctx.fillStyle = particle.color;
ctx.fill();
ctx.restore();
}
function initAutumn() {
particleCount = 120;
particles = [];
for (let i = 0; i < particleCount; i++) {
particles.push({
x: Math.random() * width,
y: Math.random() * height,
vx: (Math.random() - 0.5) * 0.8,
vy: 1 + Math.random() * 2,
sizeX: 6 + Math.random() * 6,
sizeY: 3 + Math.random() * 4,
angle: Math.random() * Math.PI * 2,
angleSpeed: (Math.random() - 0.5) * 0.05,
color: `rgba(${150 + Math.random() * 80}, ${60 + Math.random() * 40}, ${20 + Math.random() * 30}, ${0.7 + Math.random() * 0.3})`
});
}
}
// 冬天:雪花
function winterDraw(particle) {
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2);
ctx.fillStyle = particle.color;
ctx.fill();
// 偶尔加一点六角星的感觉(简化)
if (particle.size > 2) {
ctx.beginPath();
for (let i = 0; i < 6; i++) {
const angle = (i * Math.PI * 2 / 6) + particle.angle;
const x2 = particle.x + Math.cos(angle) * particle.size * 0.8;
const y2 = particle.y + Math.sin(angle) * particle.size * 0.8;
ctx.lineTo(x2, y2);
}
ctx.closePath();
ctx.fillStyle = 'rgba(255,255,255,0.9)';
ctx.fill();
}
}
function initWinter() {
particleCount = 300;
particles = [];
for (let i = 0; i < particleCount; i++) {
particles.push({
x: Math.random() * width,
y: Math.random() * height,
vx: (Math.random() - 0.5) * 0.6,
vy: 1 + Math.random() * 2.5,
size: 2 + Math.random() * 4,
angle: Math.random() * Math.PI * 2,
color: `rgba(255, 255, 255, ${0.6 + Math.random() * 0.4})`
});
}
}
// 根据季节选择初始化函数和绘制逻辑
let updateParticle = null;
let drawExtra = null;
switch (SEASON) {
case 'spring':
initSpring();
updateParticle = function(p) {
p.x += p.vx;
p.y += p.vy;
if (p.x < -20) p.x = width + 20;
if (p.x > width + 20) p.x = -20;
if (p.y > height + 20) {
p.y = -20;
p.x = Math.random() * width;
}
if (p.y < -20) p.y = height + 20;
};
customDraw = springDraw;
drawExtra = null;
break;
case 'summer':
initSummer();
updateParticle = function(p) {
p.x += p.vx;
p.y += p.vy;
if (p.x < -20) p.x = width + 20;
if (p.x > width + 20) p.x = -20;
if (p.y > height + 20) {
p.y = -20;
p.x = Math.random() * width;
}
if (p.y < -20) p.y = height + 20;
};
customDraw = summerDraw;
drawExtra = drawSun;
break;
case 'autumn':
initAutumn();
updateParticle = function(p) {
p.x += p.vx;
p.y += p.vy;
p.angle += p.angleSpeed;
if (p.x < -30) p.x = width + 30;
if (p.x > width + 30) p.x = -30;
if (p.y > height + 30) {
p.y = -30;
p.x = Math.random() * width;
}
if (p.y < -30) p.y = height + 30;
};
customDraw = autumnDraw;
drawExtra = null;
break;
case 'winter':
initWinter();
updateParticle = function(p) {
p.x += p.vx;
p.y += p.vy;
p.angle += 0.02;
if (p.x < -10) p.x = width + 10;
if (p.x > width + 10) p.x = -10;
if (p.y > height + 10) {
p.y = -10;
p.x = Math.random() * width;
}
if (p.y < -10) p.y = height + 10;
};
customDraw = winterDraw;
drawExtra = null;
break;
}
// 动画循环
function animate() {
if (!canvas.isConnected) return; // 防止画布被移除后继续
ctx.clearRect(0, 0, width, height);
// 根据不同季节添加半透明背景拖尾效果(制造动态模糊)
if (SEASON === 'summer') {
ctx.fillStyle = 'rgba(255, 200, 100, 0.05)';
ctx.fillRect(0, 0, width, height);
} else if (SEASON === 'winter') {
ctx.fillStyle = 'rgba(100, 150, 200, 0.08)';
ctx.fillRect(0, 0, width, height);
} else {
ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';
ctx.fillRect(0, 0, width, height);
}
for (let p of particles) {
updateParticle(p);
if (customDraw) customDraw(p);
else {
// fallback
ctx.fillStyle = particleColor;
ctx.fillRect(p.x, p.y, particleSize, particleSize);
}
}
if (drawExtra) drawExtra();
animationId = requestAnimationFrame(animate);
}
function initParticles() {
// 重新初始化所有粒子(适应新尺寸)
switch (SEASON) {
case 'spring': initSpring(); break;
case 'summer': initSummer(); break;
case 'autumn': initAutumn(); break;
case 'winter': initWinter(); break;
}
}
resizeCanvas();
initParticles();
animate();
// 页面可见性变化时暂停/恢复动画(节省资源)
document.addEventListener('visibilitychange', function() {
if (document.hidden) {
if (animationId) cancelAnimationFrame(animationId);
animationId = null;
} else {
if (!animationId) animate();
}
});
});
})();
Gmeek使用方法
1.将上述代码保存为 season.js.
2.把 season.js 上传到你的 Gmeek 仓库的 static/文件夹中。
3.在你的 Gmeek 项目根目录下的 config.json 文件中,找到allHead 配置项,添加引用:
"allHead": "<script src='/static/season.js'></script>"
4.提交并推送代码,等待GitHub Pages 重新部署后,刷新页面即可看到根据当前季节变化的动态背景。
效果说明
春季(3-5月):斜向飘落的蓝色雨丝,有横向飘移感。
夏季(6-8月):闪烁的橙色光点缓缓上升/飘移,右上角有旋转光线的太阳。
秋季(9-11月):彩色落叶旋转飘落,形状为椭圆。
冬季(12-2月):白色雪花缓缓飘落,偶尔有星形结构。
自定义调整
你可以根据需要修改以下参数(在对应的 initXXX函数中):
粒子数量:particleCount
粒子速度:vy的随机范围
颜色透明度等
如果你希望背景颜色随季节变化,可以在 CSS 中设置 body 的背景色,由于 Canvas 是透明的(只有粒子),底层颜色会透出来。
注意事项
该脚本会在 中动态插入一个元素,并设置 z-index: -1,因此不会遮挡页面内容。
如果你的主题已有背景色或背景图,可能会与 Canvas 重叠。你可以调整 Canvas的background-color 或修改主题背景为透明。
夏季的太阳绘制在右上角,如果你的网站右上角有重要元素(如菜单),可能会遮挡。你可以修改 drawSun 中的 sunX,sunY坐标来调整位置。
潜在问题
如果用户博客本身已有大量复杂DOM或高频重绘(如滚动视差),叠加本动画可能导致卡顿。
秋季的 ctx.ellipse + rotate + translate 是相对最耗时的操作。
夏季的太阳光线每帧重绘12条带透明度的线条,开销中等。
如何进一步降低性能消耗
如果你希望更省资源,可以:
1.减少粒子数量:例如春季200、冬季200,视觉效果依然不错。
2.简化秋季:将落叶改为普通圆形或矩形,取消旋转。
3.降低帧率:不是每帧都重绘,可以设置每隔一帧更新(但动画会变"跳”)。
4.使用 CSS 动画代替:但实现复杂动态(雨丝斜落、落叶旋转)不如 Canvas 灵活。
性能结论
放心使用,除非你的目标用户大量使用10年前的设备或非常在意续航,否则这个动画不会成为性能瓶颈。如果你仍担心,可以保留代码,实际部署后自己用手机访问测试一下,再决定是否调整粒子数量。
重要说明
有的不用添加下面的,每个人的样式结构可能不一样
重要说明
gmeek使用修复
修复顶部导航栏切换暗亮主题图标和功能BUG
只有亮和暗两个按钮,因为那个自动的auto没有什么功能,反正我觉得没用…
// === 主题持久化 + 按钮修复(保留原生图标) ===
(function() {
const STORAGE_KEY = 'meek theme';
function getIconPath(mode) {
// 尝试使用 Gmeek 原生的 IconList
if (window.IconList) {
return mode === 'dark' ? (window.IconList.moon || window.IconList.dark) : (window.IconList.sun || window.IconList.light);
}
// 后备图标(与原风格一致)
return mode === 'dark'
? 'M17.5 9.5a6 6 0 011.5 4 6.5 6.5 0 11-7-7 6 6 0 015.5 3z'
: 'M8 10.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5zM8 12a4 4 0 100-8 4 4 0 000 8z';
}
function setTheme(mode, btn) {
mode = mode === 'dark' ? 'dark' : 'light';
document.documentElement.setAttribute('data-color-mode', mode);
localStorage.setItem(STORAGE_KEY, mode);
if (btn) {
btn.setAttribute('d', getIconPath(mode));
btn.parentNode.style.color = mode === 'dark' ? '#00ff00' : '#ff5000';
}
}
function initTheme() {
let btn = document.getElementById('themeSwitch');
if (!btn) {
setTimeout(initTheme, 50);
return;
}
// 读取保存的主题,优先 localStorage
let savedMode = localStorage.getItem(STORAGE_KEY);
let currentMode = savedMode || document.documentElement.getAttribute('data-color-mode') || 'light';
// 强制应用正确的主题
setTheme(currentMode, btn);
// 绑定切换事件(覆盖原生)
btn.parentNode.onclick = (e) => {
e.stopPropagation();
let newMode = document.documentElement.getAttribute('data-color-mode') === 'light' ? 'dark' : 'light';
setTheme(newMode, btn);
};
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTheme);
} else {
initTheme();
}
window.themeSettings = window.themeSettings || { dark: [], light: [], auto: [] };
window.theme = window.theme || 'light';
})();
添加原自动按钮
为了接近原系统,所以另外添加的那个auto自动按钮,虽然没什么用,但是还是添加了,为了后期万一哪一天适配好了呢?
覆盖上面的修复代码就好了
/*修复gmeek顶部导航栏的切换暗亮图标错误空白问题*/
// === 主题持久化 + 按钮修复(支持 light / dark / auto,保留原生图标) ===
(function() {
const STORAGE_KEY = 'meek theme';
// 获取原生图标映射(优先使用 Gmeek 的 IconList,auto 使用原生灰色箭头圆环)
const getIcon = (mode) => {
if (window.IconList) {
if (mode === 'dark') return window.IconList.moon || window.IconList.dark;
if (mode === 'light') return window.IconList.sun || window.IconList.light;
if (mode === 'auto') return window.IconList.auto || 'M1.705 8.005a.75.75 0 0 1 .834.656 5.5 5.5 0 0 0 9.592 2.97l-1.204-1.204a.25.25 0 0 1 .177-.427h3.646a.25.25 0 0 1 .25.25v3.646a.25.25 0 0 1-.427.177l-1.38-1.38A7.002 7.002 0 0 1 1.05 8.84a.75.75 0 0 1 .656-.834ZM8 2.5a5.487 5.487 0 0 0-4.131 1.869l1.204 1.204A.25.25 0 0 1 4.896 6H1.25A.25.25 0 0 1 1 5.75V2.104a.25.25 0 0 1 .427-.177l1.38 1.38A7.002 7.002 0 0 1 14.95 7.16a.75.75 0 0 1-1.49.178A5.5 5.5 0 0 0 8 2.5Z';
}
// 后备图标
if (mode === 'dark') return 'M17.5 9.5a6 6 0 011.5 4 6.5 6.5 0 11-7-7 6 6 0 015.5 3z';
if (mode === 'light') return 'M8 10.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5zM8 12a4 4 0 100-8 4 4 0 000 8z';
};
// 根据 mode 获取实际显示的主题(auto 时跟随系统)
const getEffectiveTheme = (mode) => {
if (mode === 'auto') {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
return mode === 'dark' ? 'dark' : 'light';
};
// 应用主题
function applyTheme(mode, btn) {
const effective = getEffectiveTheme(mode);
document.documentElement.setAttribute('data-color-mode', effective);
localStorage.setItem(STORAGE_KEY, mode);
if (btn) {
btn.setAttribute('d', getIcon(mode));
if (mode === 'auto') {
btn.parentNode.style.color = '#6c757d'; // auto 固定灰色
} else {
btn.parentNode.style.color = effective === 'dark' ? '#00ff00' : '#ff5000';
}
}
}
// 监听系统主题变化(仅在 auto 模式下需要重新应用根元素属性)
let systemWatcher = null;
function watchSystemTheme(btn) {
if (systemWatcher) return;
const media = window.matchMedia('(prefers-color-scheme: dark)');
const handler = () => {
const currentMode = localStorage.getItem(STORAGE_KEY);
if (currentMode === 'auto') {
const effective = getEffectiveTheme('auto');
document.documentElement.setAttribute('data-color-mode', effective);
// 按钮颜色不变(始终灰色),图标不变
}
};
media.addEventListener('change', handler);
systemWatcher = handler;
}
// 初始化
function initTheme() {
let btn = document.getElementById('themeSwitch');
if (!btn) {
setTimeout(initTheme, 50);
return;
}
let savedMode = localStorage.getItem(STORAGE_KEY);
if (!savedMode) {
savedMode = document.documentElement.getAttribute('data-color-mode') === 'auto' ? 'auto' : 'light';
}
applyTheme(savedMode, btn);
btn.parentNode.onclick = (e) => {
e.stopPropagation();
let current = localStorage.getItem(STORAGE_KEY) || 'light';
let next = current === 'light' ? 'dark' : (current === 'dark' ? 'auto' : 'light');
applyTheme(next, btn);
};
watchSystemTheme(btn);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTheme);
} else {
initTheme();
}
// 修补原生 Gmeek 脚本的缺失变量(避免控制台报错)
window.themeSettings = window.themeSettings || { dark: [], light: [], auto: [] };
window.theme = window.theme || 'light';
})();最后说明
最通用的气象划分
气象部门为了统计方便,通常按公历月份固定划分四季,这也是目前最主流的说法:
春季:3 月、4 月、5 月 。
夏季:6 月、7 月、8 月 。
秋季:9 月、10 月、11 月 。
冬季:12 月、次年 1 月、2 月 。