收起左侧

Sun-panel美化个人探索之四季插件

3
回复
751
查看
[ 复制链接 ]

3

主题

5

回帖

0

牛值

江湖小虾

2025-1-10 13:12:24 显示全部楼层 阅读模式

[i=s] 本帖最后由 jamyer 于 2025-1-10 19:48 编辑 [/i]<br /> <br />

前言

话不多说,具体效果如下:

<iframe src="https://player.bilibili.com/player.html?isOutside=true&aid=113802171388400&bvid=BV156c7eYEck&cid=27791067621&p=1&danmaku=0" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe>

一键部署方法:

一键部署代码包:https://s.fnnas.net/s/82578fa513b84d1ea0,密码:sadwwww

操作方法:

1.all.js加载 /custom/Seasons/leaves.js /custom/Seasons/rain.js /custom/Seasons/sakura.js /custom/Seasons/Seasons.js /custom/Seasons/snow.js

2.复制custom和uploads到自己的Sun-panel配置目录

3.畅享,可以尝试自己更改图片,其中auto选项为按照当前的季节自动选择季节效果(春天樱花,夏天下雨,秋天枫叶飘落,冬天雪花),但是默认还是none(即无季节效果)

4.建议不要修改动画,每个季节的动画不是单纯换个图片,春天樱花没有风效,夏天的雨不打斜,秋天的落叶有无序飘落逻辑,冬天的雪花速度较慢,一切都是经过调试的

代码细谈:

春天(樱花):

// ====================== Sakura.js ======================
// ====================== 樱花效果 ======================
class SakuraEffect {
    constructor() {
        this.canvas = document.createElement('canvas');
        this.ctx = this.canvas.getContext('2d');
        this.sakuraList = [];
        this.isActive = false;
        this.img = new Image();
        this.img.src = '/uploads/Seasons/Sakura/Sakura.svg'; // 樱花图片路径
        this.initCanvas();
    }

    // 初始化画布
    initCanvas() {
        this.canvas.style.position = 'fixed';
        this.canvas.style.left = '0';
        this.canvas.style.top = '0';
        this.canvas.style.pointerEvents = 'none';
        this.canvas.width = window.innerWidth;
        this.canvas.height = window.innerHeight;
        document.body.appendChild(this.canvas);
    }

    // 初始化樱花
    initSakura() {
        this.sakuraList = []; // 清空之前的樱花
        for (let i = 0; i < 50; i++) {
            this.sakuraList.push(new Sakura(
                this.getRandom('x'),
                this.getRandom('y'),
                this.getRandom('s'),
                this.getRandom('r'),
                {
                    x: this.getRandom('fnx'),
                    y: this.getRandom('fny'),
                    r: this.getRandom('fnr')
                }
            ));
        }
    }

    // 启动樱花效果
    start() {
        if (!this.isActive) {
            this.isActive = true;
            this.initSakura(); // 重新初始化樱花
            this.animate();
        }
    }

    // 停止樱花效果
    stop() {
        this.isActive = false;
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.sakuraList = []; // 清空樱花数组,释放资源
    }

    // 动画循环
    animate() {
        if (!this.isActive) return;

        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.sakuraList.forEach(sakura => {
            sakura.update();
            sakura.draw(this.ctx, this.img);
        });
        requestAnimationFrame(() => this.animate());
    }

    // 随机数生成器
    getRandom(option) {
        switch (option) {
            case 'x': return Math.random() * window.innerWidth;
            case 'y': return Math.random() * window.innerHeight;
            case 's': return Math.random();
            case 'r': return Math.random() * 6;
            case 'fnx': return (x) => x + 0.5 * (Math.random() - 0.5) - 1.7;
            case 'fny': return (y) => y + 1.5 + Math.random() * 0.7;
            case 'fnr': return (r) => r + Math.random() * 0.03;
            default: throw new Error("Invalid option for getRandom");
        }
    }
}

// 樱花类
class Sakura {
    constructor(x, y, s, r, fn) {
        this.x = x;
        this.y = y;
        this.s = s;
        this.r = r;
        this.fn = fn;
    }

    // 更新樱花位置
    update() {
        this.x = this.fn.x(this.x, this.y);
        this.y = this.fn.y(this.y, this.y);
        this.r = this.fn.r(this.r);
        if (this.x > window.innerWidth || this.x < 0 || this.y > window.innerHeight || this.y < 0) {
            this.resetPosition();
        }
    }

    // 重置樱花位置
    resetPosition() {
        this.r = Math.random() * 6;
        if (Math.random() > 0.4) {
            this.x = Math.random() * window.innerWidth;
            this.y = 0;
        } else {
            this.x = window.innerWidth;
            this.y = Math.random() * window.innerHeight;
        }
        this.s = Math.random();
    }

    // 绘制樱花
    draw(ctx, img) {
        ctx.save();
        ctx.translate(this.x, this.y);
        ctx.rotate(this.r);
        ctx.drawImage(img, 0, 0, 40 * this.s, 40 * this.s);
        ctx.restore();
    }
}

// ====================== 樱花按钮 ======================
function createSakuraToggleButton(sakuraEffect) {
    const button = document.createElement('img');
    button.src = '/uploads/Seasons/Sakura/button.svg'; // 按钮图片路径
    button.style.position = 'fixed';
    button.style.left = '10px'; // 按钮水平位置
    button.style.top = '10px';
    button.style.cursor = 'pointer';
    button.style.zIndex = '1000';
    button.style.width = '30px'; // 按钮宽度
    button.style.height = '30px'; // 按钮高度
    button.style.opacity = '0.5'; // 初始状态为变暗(关闭)
    button.id = 'sakura-toggle-button';
    document.body.appendChild(button);

    // 定义樱花按钮的旋转动画
    const sakuraStyle = document.createElement('style');
    sakuraStyle.innerHTML = `
        @keyframes rotate-sakura {
            from { transform: rotate(0deg); }
            to { transform: rotate(360deg); }
        }
        .rotate-button-sakura {
            animation: rotate-sakura 2s linear infinite; /* 2秒一圈,无限循环 */
        }
    `;
    document.head.appendChild(sakuraStyle);

    // 樱花按钮的点击逻辑
    button.addEventListener('click', function() {
        if (sakuraEffect.isActive) {
            sakuraEffect.stop();
            button.classList.remove('rotate-button-sakura'); // 停止旋转
            button.style.opacity = '0.5'; // 按钮变暗表示关闭
        } else {
            sakuraEffect.start();
            button.classList.add('rotate-button-sakura'); // 开始旋转
            button.style.opacity = '1'; // 按钮变亮表示开启
        }
    });
}

// ====================== 初始化 ======================
const sakuraEffect = new SakuraEffect();
createSakuraToggleButton(sakuraEffect);

// 窗口大小变化时调整画布大小
window.addEventListener('resize', () => {
    sakuraEffect.canvas.width = window.innerWidth;
    sakuraEffect.canvas.height = window.innerHeight;
});

感觉樱花没啥好说的,网上一搜一大把。。。



夏天(雨滴):

// 雨滴效果 rain.js
class RainEffect {
    constructor(canvas) {
        this.canvas = canvas;
        this.ctx = canvas.getContext('2d');
        this.rainDrops = [];
        this.rainCount = 250; // 雨滴数量
        this.isActive = false; // 默认关闭
        this.raindropImage = new Image(); // 加载 SVG 雨滴图片
        this.raindropImage.src = '/uploads/Seasons/rain/rain.svg'; // 替换为你的 SVG 雨滴图片路径
        this.raindropImage.onload = () => {
            this.init(); // 图片加载完成后初始化
        };
    }

    init() {
        for (let i = 0; i < this.rainCount; i++) {
            this.rainDrops.push(new RainDrop(this.canvas));
        }
    }

    start() {
        this.isActive = true;
        this.animate();
    }

    stop() {
        this.isActive = false;
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // 清除画布
    }

    animate() {
        if (!this.isActive) return;

        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.rainDrops.forEach(drop => {
            drop.update();
            drop.draw(this.ctx, this.raindropImage); // 传递 SVG 图片
        });
        requestAnimationFrame(() => this.animate());
    }
}

class RainDrop {
    constructor(canvas) {
        this.canvas = canvas;
        this.x = Math.random() * canvas.width; // 随机水平位置
        this.y = Math.random() * canvas.height * -1; // 从画布顶部开始
        this.opacity = Math.random(); // 随机透明度
        this.animationDuration = (Math.random() + 0.5) * 2; // 随机动画时长
        this.animationDelay = (Math.random() * 2 - 1) * 1000; // 随机延迟
        this.size = Math.random(); // 随机大小
        this.startTime = Date.now() + this.animationDelay; // 动画开始时间
    }

    update() {
        const currentTime = Date.now();
        const elapsedTime = currentTime - this.startTime;

        if (elapsedTime < 0) return; // 如果动画还未开始,跳过

        // 计算动画进度(0 到 1)
        const progress = Math.min(elapsedTime / (this.animationDuration * 1000), 1);

        // 更新雨滴位置
        this.y = progress * this.canvas.height;

        // 更新透明度
        if (progress > 0.9) {
            this.opacity = 1 - (progress - 0.9) * 10; // 最后 10% 逐渐消失
        } else {
            this.opacity = Math.min(this.opacity + 0.01, 1); // 逐渐变亮
        }

        // 如果动画结束,重置雨滴
        if (progress >= 1) {
            this.reset();
        }
    }

    reset() {
        this.x = Math.random() * this.canvas.width; // 随机水平位置
        this.y = Math.random() * this.canvas.height * -1; // 从画布顶部重新开始
        this.opacity = Math.random(); // 重置透明度
        this.animationDuration = (Math.random() + 0.5) * 2; // 重置动画时长
        this.animationDelay = (Math.random() * 2 - 1) * 1000; // 重置延迟
        this.size = Math.random(); // 重置大小
        this.startTime = Date.now() + this.animationDelay; // 重置动画开始时间
    }

    draw(ctx, image) {
        ctx.save();
        ctx.globalAlpha = this.opacity; // 设置透明度
        ctx.drawImage(
            image,
            this.x, // x 坐标
            this.y, // y 坐标
            5 * this.size, // 宽度(根据 SVG 的 viewBox 宽度 5 缩放)
            50 * this.size // 高度(根据 SVG 的 viewBox 高度 50 缩放)
        );
        ctx.restore();
    }
}

// 创建雨滴效果
const rainCanvas = document.createElement('canvas');
rainCanvas.style.position = 'fixed';
rainCanvas.style.left = '0';
rainCanvas.style.top = '0';
rainCanvas.style.pointerEvents = 'none';
rainCanvas.width = window.innerWidth;
rainCanvas.height = window.innerHeight;
document.body.appendChild(rainCanvas);

const rainEffect = new RainEffect(rainCanvas);

// 创建雨滴开关按钮
function createRainToggleButton() {
    const button = document.createElement('img');
    button.src = '/uploads/Seasons/rain/button.svg'; // 替换为你的雨滴按钮图片路径
    button.style.position = 'fixed';
    button.style.left = '70px'; // 放在樱花按钮右侧
    button.style.top = '10px';
    button.style.cursor = 'pointer';
    button.style.zIndex = '1000';
    button.style.width = '30px'; // 设置按钮宽度
    button.style.height = '30px'; // 设置按钮高度
    button.style.opacity = '0.5'; // 初始状态为变暗(关闭)
    button.id = 'rain-toggle-button';
    document.body.appendChild(button);

    // 点击按钮切换状态
    button.addEventListener('click', function() {
        if (rainEffect.isActive) {
            rainEffect.stop();
            button.style.opacity = '0.5'; // 按钮变暗表示关闭
        } else {
            rainEffect.start();
            button.style.opacity = '1'; // 按钮变亮表示开启
        }
    });
}

// 初始化雨滴按钮
createRainToggleButton();

// 窗口大小变化时调整画布大小
window.addEventListener('resize', () => {
    rainCanvas.width = window.innerWidth;
    rainCanvas.height = window.innerHeight;
});

雨滴效果其实要注意的点就稍微多了一点,想要观感好,除了图片要选对(太重要了,网上找了各种资源,都觉得多多少少差点意思,然后自己画了个svg之后发现真的是图的问题!),还要让调教雨滴的各种参数(数量,大小范围,透明度等),说起来比较简单,但是一次次刷新网页看效果的时候一点儿也不简单。。



秋天(落叶):

// ====================== leaves.js ======================
// ====================== 枫叶效果 ======================
class MapleEffect {
    constructor(canvas) {
        this.canvas = canvas;
        this.ctx = canvas.getContext('2d');
        this.leaves = [];
        this.leafCount = 50; // 枫叶总数
        this.initialLeafCount = 10; // 初始枫叶数量
        this.isActive = false; // 默认关闭
        this.leafImages = []; // 存储多张枫叶图片
        this.leafImagePaths = [
            '/uploads/Seasons/leaves/leaf1.png', // 替换为你的枫叶图片路径
            '/uploads/Seasons/leaves/leaf2.png', // 替换为你的枫叶图片路径
            '/uploads/Seasons/leaves/leaf3.png', // 替换为你的枫叶图片路径
            '/uploads/Seasons/leaves/leaf4.png', // 替换为你的枫叶图片路径
            '/uploads/Seasons/leaves/leaf5.png', // 替换为你的枫叶图片路径
        ];
        this.preloadImages(); // 预加载图片
    }

    // 预加载图片
    preloadImages() {
        this.leafImagePaths.forEach(path => {
            const img = new Image();
            img.src = path;
            this.leafImages.push(img);
        });
    }

    // 初始化部分枫叶
    init(count) {
        for (let i = 0; i < count; i++) {
            this.leaves.push(new MapleLeaf(this.canvas, this.leafImages));
        }
    }

    // 逐步增加枫叶数量
    addLeavesGradually() {
        if (this.leaves.length < this.leafCount) {
            const remainingLeaves = this.leafCount - this.leaves.length;
            const leavesToAdd = Math.min(10, remainingLeaves); // 每次增加10片
            this.init(leavesToAdd);
            setTimeout(() => this.addLeavesGradually(), 1000); // 每1秒增加一次
        }
    }

    // 启动枫叶效果
    start() {
        if (!this.isActive) {
            this.isActive = true;
            this.init(this.initialLeafCount); // 初始化部分枫叶
            this.animate(); // 开始动画
            this.addLeavesGradually(); // 逐步增加枫叶数量
        }
    }

    // 停止枫叶效果
    stop() {
        this.isActive = false;
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // 清除画布
        this.leaves = []; // 清空枫叶数组,释放资源
    }

    // 动画循环
    animate() {
        if (!this.isActive) return;

        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.leaves.forEach(leaf => {
            leaf.update();
            leaf.draw(this.ctx); // 绘制枫叶
        });
        requestAnimationFrame(() => this.animate());
    }
}

// 枫叶类
class MapleLeaf {
    constructor(canvas, leafImages) {
        this.canvas = canvas;
        this.leafImages = leafImages;
        this.image = this.getRandomImage(); // 随机选择一张图片
        this.x = Math.random() * canvas.width; // 随机水平位置
        this.y = Math.random() * canvas.height * -1; // 从画布顶部开始
        this.speed = Math.random() * 1 + 0.5; // 随机速度
        this.rotation = Math.random() * 360; // 随机旋转角度
        this.rotationSpeed = Math.random() * 0.05 - 0.025; // 随机旋转速度
        this.opacity = Math.random() * 0.5 + 0.2; // 随机透明度
        this.size = Math.random() * 0.5 + 0.5; // 随机大小
        this.wind = Math.random() * 2 - 1; // 随机风的影响
        this.curve = Math.random() * 2 - 1; // 随机曲线运动
    }

    // 随机选择一张图片
    getRandomImage() {
        return this.leafImages[Math.floor(Math.random() * this.leafImages.length)];
    }

    update() {
        this.y += this.speed; // 下落
        this.x += this.wind + Math.sin(this.y * 0.01) * this.curve; // 水平偏移 + 曲线运动
        this.rotation += this.rotationSpeed; // 旋转

        // 如果枫叶超出画布,重置到顶部
        if (this.y > this.canvas.height) {
            this.reset();
        }
    }

    reset() {
        this.image = this.getRandomImage(); // 随机选择一张图片
        this.x = Math.random() * this.canvas.width; // 随机水平位置
        this.y = Math.random() * this.canvas.height * -1; // 从画布顶部重新开始
        this.speed = Math.random() * 1 + 0.5; // 重置速度
        this.rotation = Math.random() * 360; // 重置旋转角度
        this.rotationSpeed = Math.random() * 0.05 - 0.025; // 重置旋转速度
        this.opacity = Math.random() * 0.5 + 0.2; // 重置透明度
        this.size = Math.random() * 0.5 + 0.5; // 重置大小
        this.wind = Math.random() * 2 - 1; // 重置风的影响
        this.curve = Math.random() * 2 - 1; // 重置曲线运动
    }

    draw(ctx) {
        ctx.save();
        ctx.globalAlpha = this.opacity; // 设置透明度
        ctx.translate(this.x, this.y); // 移动到枫叶位置
        ctx.rotate((this.rotation * Math.PI) / 180); // 旋转
        ctx.drawImage(
            this.image,
            -20 * this.size, // x 偏移(居中)
            -20 * this.size, // y 偏移(居中)
            40 * this.size, // 宽度
            40 * this.size // 高度
        );
        ctx.restore();
    }
}

// 创建枫叶效果
const mapleCanvas = document.createElement('canvas');
mapleCanvas.style.position = 'fixed';
mapleCanvas.style.left = '0';
mapleCanvas.style.top = '0';
mapleCanvas.style.pointerEvents = 'none';
mapleCanvas.width = window.innerWidth;
mapleCanvas.height = window.innerHeight;
document.body.appendChild(mapleCanvas);

const mapleEffect = new MapleEffect(mapleCanvas);

// 创建枫叶开关按钮
function createMapleToggleButton() {
    const button = document.createElement('img');
    button.src = '/uploads/Seasons/leaves/button.svg'; // 替换为你的枫叶按钮图片路径
    button.style.position = 'fixed';
    button.style.left = '130px'; // 放在雨滴按钮右侧
    button.style.top = '10px';
    button.style.cursor = 'pointer';
    button.style.zIndex = '1000';
    button.style.width = '30px'; // 设置按钮宽度
    button.style.height = '30px'; // 设置按钮高度
    button.style.opacity = '0.5'; // 初始状态为变暗(关闭)
    button.id = 'maple-toggle-button';
    document.body.appendChild(button);

    // 点击按钮切换状态
    button.addEventListener('click', function() {
        if (mapleEffect.isActive) {
            mapleEffect.stop();
            button.style.opacity = '0.5'; // 按钮变暗表示关闭
        } else {
            mapleEffect.start();
            button.style.opacity = '1'; // 按钮变亮表示开启
        }
    });
}

// 初始化枫叶按钮
createMapleToggleButton();

// 窗口大小变化时调整画布大小
window.addEventListener('resize', () => {
    mapleCanvas.width = window.innerWidth;
    mapleCanvas.height = window.innerHeight;
});

这个是纯纯的血压飙升区,首先要模拟树叶落下的感觉,左飘然后右飘(水平偏移、曲线运动、风力影响等等共同决定),其次呢树叶不同于樱花、雨滴、雪花,需要多种树叶才能显得不那么单调,然后一开始我用了五张3-5m的矢量图片来作为图像,但是运行过程中总会偶尔卡那么几下,对此我尝试了预加载、预渲染、分批加载、逐步增加屏幕内枫叶数量等方式,但是都无济于事,无奈之下只好尝试降低图片质量,事实证明,质量一降低,立马就不卡了。



冬天(雪花):

// ====================== snow.js ======================
// ====================== 雪花效果 ======================
class SnowEffect {
    constructor() {
        this.canvas = document.createElement('canvas');
        this.ctx = this.canvas.getContext('2d');
        this.snowflakes = [];
        this.snowflakeCount = 100; // 雪花数量
        this.isActive = false; // 默认关闭
        this.snowflakeImage = new Image(); // 加载雪花图片
        this.snowflakeImage.src = '/uploads/Seasons/snow/snow.svg'; // 雪花图片路径

        // 全局风力和曲线运动
        this.wind = 0.1; // 较小的风力,控制轻微的水平飘动
        this.curve = 0.1; // 较小的曲线运动,控制轻微摆动

        this.initCanvas();
    }

    // 初始化画布
    initCanvas() {
        this.canvas.style.position = 'fixed';
        this.canvas.style.left = '0';
        this.canvas.style.top = '0';
        this.canvas.style.pointerEvents = 'none';
        this.canvas.width = window.innerWidth;
        this.canvas.height = window.innerHeight;
        document.body.appendChild(this.canvas);
    }

    // 初始化雪花
    initSnowflakes() {
        this.snowflakes = []; // 清空之前的雪花
        for (let i = 0; i < this.snowflakeCount; i++) {
            this.snowflakes.push(new Snowflake(this.canvas, this.wind)); // 传递风力方向
        }
    }

    // 启动雪花效果
    start() {
        this.isActive = true;
        this.initSnowflakes(); // 每次启动时重新初始化雪花
        this.animate();
    }

    // 停止雪花效果
    stop() {
        this.isActive = false;
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.snowflakes = []; // 清空雪花数组,释放资源
    }

    // 动画循环
    animate() {
        if (!this.isActive) return;

        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.snowflakes.forEach(snowflake => {
            snowflake.update(this.wind, this.curve); // 传递全局风力和曲线值
            snowflake.draw(this.ctx, this.snowflakeImage);
        });
        requestAnimationFrame(() => this.animate());
    }
}

// 雪花类
class Snowflake {
    constructor(canvas, wind) {
        this.canvas = canvas;
        this.wind = wind; // 记录当前风力方向

        // 主要生成区域:屏幕上方
        if (Math.random() < 0.8) { // 80% 的雪花从屏幕上方生成
            this.x = Math.random() * canvas.width; // 随机水平位置
        } else if (wind > 0) {
            // 20% 的雪花从左侧边界外生成(仅当风力向右时)
            this.x = Math.random() * canvas.width * 0.2 - canvas.width * 0.2;
        } else {
            // 无风力或风力向左,仍然从屏幕上方生成
            this.x = Math.random() * canvas.width;
        }

        this.y = Math.random() * canvas.height * -1; // 从画布顶部开始
        this.speed = Math.random() * 0.3 + 0.1; // 随机速度(较慢)
        this.rotation = Math.random() * 360; // 随机旋转角度
        this.rotationSpeed = Math.random() * 0.2 - 0.1; // 随机旋转速度(较快)
        this.opacity = Math.random() * 0.7 + 0.3; // 随机透明度
        this.size = Math.random() * 1.5 + 1; // 随机大小
    }

    // 更新雪花位置
    update(wind, curve) {
        this.y += this.speed; // 下落
        this.x += wind + Math.sin(this.y * 0.01) * curve; // 使用全局风力和曲线运动
        this.rotation += this.rotationSpeed; // 更新旋转角度

        // 如果雪花超出画布,重置到顶部
        if (this.y > this.canvas.height) {
            this.reset();
        }
    }

    // 重置雪花位置
    reset() {
        // 主要生成区域:屏幕上方
        if (Math.random() < 0.8) { // 80% 的雪花从屏幕上方生成
            this.x = Math.random() * this.canvas.width; // 随机水平位置
        } else if (this.wind > 0) {
            // 20% 的雪花从左侧边界外生成(仅当风力向右时)
            this.x = Math.random() * this.canvas.width * 0.2 - this.canvas.width * 0.2;
        } else {
            // 无风力或风力向左,仍然从屏幕上方生成
            this.x = Math.random() * this.canvas.width;
        }

        this.y = Math.random() * this.canvas.height * -1; // 从画布顶部重新开始
        this.speed = Math.random() * 0.3 + 0.1; // 重置速度
        this.rotation = Math.random() * 360; // 重置旋转角度
        this.rotationSpeed = Math.random() * 0.2 - 0.1; // 重置旋转速度
        this.opacity = Math.random() * 0.7 + 0.3; // 重置透明度
        this.size = Math.random() * 1.5 + 1; // 重置大小
    }

    // 绘制雪花
    draw(ctx, image) {
        ctx.save();
        ctx.globalAlpha = this.opacity; // 设置透明度
        ctx.translate(this.x, this.y); // 移动到雪花位置
        ctx.rotate((this.rotation * Math.PI) / 180); // 旋转

        ctx.drawImage(
            image,
            -10 * this.size, // x 偏移(居中)
            -10 * this.size, // y 偏移(居中)
            20 * this.size, // 宽度
            20 * this.size // 高度
        );
        ctx.restore();
    }
}

// ====================== 雪花按钮 ======================
function createSnowToggleButton(snowEffect) {
    const button = document.createElement('img');
    button.src = '/uploads/Seasons/snow/button.svg'; // 按钮图片路径
    button.style.position = 'fixed';
    button.style.left = '190px'; // 按钮水平位置
    button.style.top = '10px';
    button.style.cursor = 'pointer';
    button.style.zIndex = '1000';
    button.style.width = '30px'; // 按钮宽度
    button.style.height = '30px'; // 按钮高度
    button.style.opacity = '0.5'; // 初始状态为变暗(关闭)
    button.id = 'snow-toggle-button';
    document.body.appendChild(button);

    // 定义雪花按钮的旋转动画
    const snowStyle = document.createElement('style');
    snowStyle.innerHTML = `
        @keyframes rotate-snow {
            from { transform: rotate(0deg); }
            to { transform: rotate(360deg); }
        }
        .rotate-button-snow {
            animation: rotate-snow 2s linear infinite; /* 2秒一圈,无限循环 */
        }
    `;
    document.head.appendChild(snowStyle);

    // 雪花按钮的点击逻辑
    button.addEventListener('click', function() {
        if (snowEffect.isActive) {
            snowEffect.stop();
            button.classList.remove('rotate-button-snow'); // 停止旋转
            button.style.opacity = '0.5'; // 按钮变暗表示关闭
        } else {
            snowEffect.start();
            button.classList.add('rotate-button-snow'); // 开始旋转
            button.style.opacity = '1'; // 按钮变亮表示开启
        }
    });
}

// ====================== 初始化 ======================
const snowEffect = new SnowEffect();
createSnowToggleButton(snowEffect);

// 窗口大小变化时调整画布大小
window.addEventListener('resize', () => {
    snowEffect.canvas.width = window.innerWidth;
    snowEffect.canvas.height = window.innerHeight;
});

同样是老生常谈的东西了,重点就是如何控制上方和侧方的出雪量以达成雪花铺满屏幕的效果。雪花飘落的速度较慢,也不像落叶一般会左飘右飘,因此给一个风力让所有雪花往一个方向飘,让其下落过程中自转即可达成不差的效果。



控制按钮:

// ====================== 移动原有的四个季节按钮 ======================

// 获取原有按钮
const sakuraButton = document.getElementById('sakura-toggle-button');
const rainButton = document.getElementById('rain-toggle-button');
const mapleButton = document.getElementById('maple-toggle-button');
const snowButton = document.getElementById('snow-toggle-button');

// 创建菜单容器
const seasonMenu = document.createElement('div');
seasonMenu.id = 'season-menu';
seasonMenu.style.position = 'fixed';
seasonMenu.style.left = '50px';
seasonMenu.style.top = '10px';
seasonMenu.style.display = 'none'; // 初始隐藏
seasonMenu.style.flexDirection = 'row'; // 横向排列
seasonMenu.style.gap = '10px';
seasonMenu.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
seasonMenu.style.padding = '5px 10px';
seasonMenu.style.borderRadius = '5px';
seasonMenu.style.opacity = '0';
seasonMenu.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
seasonMenu.style.transform = 'translateX(-20px)';
seasonMenu.style.zIndex = '1000';
seasonMenu.style.pointerEvents = 'none'; // 初始禁用点击事件
document.body.appendChild(seasonMenu);

// 移动原有按钮到菜单中
if (sakuraButton) {
    seasonMenu.appendChild(sakuraButton);
    sakuraButton.style.position = 'static'; // 移除原有定位
    sakuraButton.style.margin = '0'; // 移除原有外边距
}

if (rainButton) {
    seasonMenu.appendChild(rainButton);
    rainButton.style.position = 'static'; // 移除原有定位
    rainButton.style.margin = '0'; // 移除原有外边距
}

if (mapleButton) {
    seasonMenu.appendChild(mapleButton);
    mapleButton.style.position = 'static'; // 移除原有定位
    mapleButton.style.margin = '0'; // 移除原有外边距
}

if (snowButton) {
    seasonMenu.appendChild(snowButton);
    snowButton.style.position = 'static'; // 移除原有定位
    snowButton.style.margin = '0'; // 移除原有外边距
}

// ====================== 创建新的四季按钮 ======================

// 创建四季按钮
const seasonButton = document.createElement('img');
seasonButton.id = 'season-button';
seasonButton.src = '/uploads/Seasons/Seasons.png'; // 替换为你的四季图标路径
seasonButton.style.position = 'fixed';
seasonButton.style.left = '10px';
seasonButton.style.top = '10px';
seasonButton.style.width = '30px'; // 设置图标大小
seasonButton.style.height = '30px'; // 设置图标大小
seasonButton.style.cursor = 'pointer';
seasonButton.style.zIndex = '1000';
document.body.appendChild(seasonButton);

// 点击四季按钮时显示/隐藏菜单
seasonButton.addEventListener('click', () => {
    event.stopPropagation(); // 阻止事件冒泡
    if (seasonMenu.style.opacity === '0') {
        seasonMenu.style.opacity = '1'; // 显示菜单
        seasonMenu.style.transform = 'translateX(0)'; // 取消偏移
        seasonMenu.style.display = 'flex'; // 显示元素
        seasonMenu.style.pointerEvents = 'auto'; // 启用点击事件
    } else {
        seasonMenu.style.opacity = '0'; // 隐藏菜单
        seasonMenu.style.transform = 'translateX(-20px)'; // 添加偏移
        seasonMenu.style.display = 'none'; // 完全隐藏元素
        seasonMenu.style.pointerEvents = 'none'; // 禁用点击事件
    }
});

// ====================== 添加 Auto 和 None 按钮 ======================

// 创建 Auto 按钮
const autoButton = document.createElement('div');
autoButton.id = 'auto-button';
autoButton.style.display = 'flex';
autoButton.style.alignItems = 'center';
autoButton.style.gap = '5px';
autoButton.style.cursor = 'pointer';

// 添加 Auto 图标
const autoIcon = document.createElement('img');
autoIcon.src = '/uploads/Seasons/Auto.png'; // 替换为你的 Auto 图标路径
autoIcon.style.width = '30px'; // 设置图标大小
autoIcon.style.height = '30px'; // 设置图标大小
autoButton.appendChild(autoIcon);

// 绑定 Auto 按钮点击事件
autoButton.addEventListener('click', () => {
    stopAllEffects(); // 关闭所有效果
    const month = new Date().getMonth() + 1;
    if (month >= 3 && month <= 5) {
        sakuraEffect.start(); // 春天
        updateButtonState('sakura-toggle-button'); // 更新按钮状态
    } else if (month >= 6 && month <= 8) {
        rainEffect.start(); // 夏天
        updateButtonState('rain-toggle-button'); // 更新按钮状态
    } else if (month >= 9 && month <= 11) {
        mapleEffect.start(); // 秋天
        updateButtonState('maple-toggle-button'); // 更新按钮状态
    } else {
        snowEffect.start(); // 冬天
        updateButtonState('snow-toggle-button'); // 更新按钮状态
    }
});
seasonMenu.appendChild(autoButton);

// 创建 None 按钮
const noneButton = document.createElement('div');
noneButton.id = 'none-button';
noneButton.style.display = 'flex';
noneButton.style.alignItems = 'center';
noneButton.style.gap = '5px';
noneButton.style.cursor = 'pointer';

// 添加 None 图标
const noneIcon = document.createElement('img');
noneIcon.src = '/uploads/Seasons/None.png'; // 替换为你的 None 图标路径
noneIcon.style.width = '30px'; // 设置图标大小
noneIcon.style.height = '30px'; // 设置图标大小
noneButton.appendChild(noneIcon);

// 绑定 None 按钮点击事件
noneButton.addEventListener('click', () => {
    stopAllEffects(); // 关闭所有效果
    updateButtonState('none-button'); // 更新按钮状态
});
seasonMenu.appendChild(noneButton);

// 关闭所有季节效果
function stopAllEffects() {
    sakuraEffect.stop();
    rainEffect.stop();
    mapleEffect.stop();
    snowEffect.stop();
}

// 更新按钮状态
function updateButtonState(activeButtonId) {
    const buttons = [
        sakuraButton,
        rainButton,
        mapleButton,
        snowButton,
        autoButton,
        noneButton,
    ];

    buttons.forEach(button => {
        if (button) {
            if (button.id === activeButtonId) {
                button.style.opacity = '1'; // 当前按钮变亮
                // 如果是樱花或雪按钮,添加旋转动画
                if (button.id === 'sakura-toggle-button') {
                    button.classList.add('rotate-button-sakura');
                } else if (button.id === 'snow-toggle-button') {
                    button.classList.add('rotate-button-snow');
                }
            } else {
                button.style.opacity = '0.5'; // 其他按钮变暗
                // 如果是樱花或雪按钮,移除旋转动画
                if (button.id === 'sakura-toggle-button') {
                    button.classList.remove('rotate-button-sakura');
                } else if (button.id === 'snow-toggle-button') {
                    button.classList.remove('rotate-button-snow');
                }
            }
        }
    });
}

// 绑定原有按钮的点击事件
if (sakuraButton) {
    sakuraButton.addEventListener('click', () => {
        stopAllEffects();
        sakuraEffect.start();
        updateButtonState('sakura-toggle-button');
    });
}

if (rainButton) {
    rainButton.addEventListener('click', () => {
        stopAllEffects();
        rainEffect.start();
        updateButtonState('rain-toggle-button');
    });
}

if (mapleButton) {
    mapleButton.addEventListener('click', () => {
        stopAllEffects();
        mapleEffect.start();
        updateButtonState('maple-toggle-button');
    });
}

if (snowButton) {
    snowButton.addEventListener('click', () => {
        stopAllEffects();
        snowEffect.start();
        updateButtonState('snow-toggle-button');
    });
}

// ====================== 初始化按钮状态 ======================
let isInitialized = false; // 标志位,确保只初始化一次

function initializeButtonState() {
    if (isInitialized) return; // 如果已经初始化,直接返回
    isInitialized = true; // 标记为已初始化

    // 获取 none-button 元素
    const noneButton = document.getElementById('none-button'); 

    // 如果按钮存在且未被点击过,则触发点击事件
    if (noneButton && !noneButton.classList.contains('clicked')) {
        noneButton.click(); // 触发 none 按钮的点击事件
        noneButton.classList.add('clicked'); // 标记按钮已被点击
    }
}

// 延迟执行初始化逻辑
setTimeout(initializeButtonState, 500); // 延迟 500 毫秒

// 页面加载完成后初始化按钮状态
window.addEventListener('load', initializeButtonState);

这里我直接在前面的四个js里面都定义好了按钮,然后再整合起来,做出了共七个按钮,其中一个是唤起按钮,图片可以自由修改,目前这个图片感觉一般,点击按钮会向右弹出六个按钮,分别是樱花(春)、雨滴(夏)、枫叶(秋)、雪花(冬)、Auto、None,默认是None,Auto的作用是按照当前的月份匹配相应的季节来选择该季节的效果。目前暂时没有想到办法来让设置动态存储起来,因此默认选择None,所以每次重载界面都要重新打开设置来设置想要的季节效果。目前按钮里面有一个不太满意的地方,Auto按钮每次页面重载的时候都会自动亮起,但是总体也不违和也就不管了,如果知道什么原因可以跟我说一下谢谢。

如果想要替换默认加载的js,可以将function initializeButtonState里面的全部noneButton替换成对应的按钮 如樱花sakuraButton、雨滴rainButto你、落叶mapleButton、雪snowButton、以及autobutton。

感谢Sun-panel三群Junius提出的bug(按钮被收起时仍存在交互性),目前已修复


结语

总觉得做得还不够好,至今还在寻找方法来实现前后端交互。。。。

收藏
送赞 3
分享

5

主题

6

回帖

0

牛值

江湖小虾

2025-2-22 11:50:28 显示全部楼层
大佬,连接过期了哇

0

主题

2

回帖

0

牛值

江湖小虾

2025-3-14 16:07:56 显示全部楼层
求大佬再分享一下文件,上面的链接过期了

0

主题

1

回帖

0

牛值

江湖小虾

2025-3-24 10:29:04 显示全部楼层
链接过期了。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则