大纲

网站背景根据季节改变,比如春天下雨细雨,夏天太阳很大,秋天落叶,冬天下雨

简述

将整个季节背景切换功能整合成一段独立的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 月‌ 。‌‌

→转载请注明出处←