【冬至活动】❄天寒情浓,构建你的冬日主题站点

hello~ :heart_eyes:小伙伴们

:dumpling:冬至快乐呀,首先感谢大家一起陪伴AIChat走过了第一个冬至!:tada::champagne:

我们现在以“ 天寒情浓,构建你的冬日主题站点 ”为主题,邀请各位站长设计自己的站点的特色冬日主题:snowman:,详细要求如下:

:gift:奖励设置


第一名(1名):AimHub 20刀额度兑换码 :heavy_plus_sign: 旗舰版首发体验邀请
第二名(1名):AimHub 15刀额度兑换码
第三名(1名):AimHub 10刀额度兑换码

:bell:活动要求


  • 站长自行设计主题元素,主要通过AIChat站点后台的 HTML及全局JS自定义 来实现,对于 二次开发 的站点也允许参与;

  • 站长 回复此帖子并附上实现过程(必须带效果图及代码) ,即为参加此次活动;

  • 最后根据 点赞:heart: 数量最多的前三位参赛帖子来依次给予相应名次的奖励

  • 活动时间: 2023-12-21T16:00:00Z2023-12-24T16:00:00Z

  • 设计可参考:冬至福利:动态下雪JS代码

注:如出现相同赞的情况,AIChat项目组有权进行最终判定和排序,此次活动的解释权由AIChat项目组所有


最后祝大家玩的开心,玩的高兴,么么~:kissing_smiling_eyes:

2 个赞
  1. 增加雪花颜色的随机性:目前的代码中雪花是白色的。可以通过增加随机选择颜色的逻辑,让雪花显示不同的颜色。
  2. 增加雪花旋转效果:为了使雪花下落时看起来更自然,可以添加一些旋转动画。
  3. 控制雪花密度:添加一个可以控制雪花密度的参数,让用户可以调整页面上雪花的数量。
  4. 响应式设*:确保雪花效果能在不同大小的屏幕上良好展示,包括手机和平板电脑。
  5. 性能优化:为了避免大量的雪花造成页面性能问题,可以设置雪花的最大数量限制。
  6. 加深雪花颜色:修改雪花的颜色数组,使用更深的颜色,以更好地模拟真实的冬天气氛。
  7. 改进雪花下落的动态效果:可以使雪花在下落过程中出现轻微的左右摇摆,模拟真实的雪花飘落效果。
function snow() {
    var flake = document.createElement('div');
    flake.innerHTML = '❅'; // 可选雪花字符
    flake.style.cssText = 'position:absolute;color:#fff;';

    var documentHeight = window.innerHeight;
    var documentWidth = window.innerWidth;

    var millisec = 10; // 生成雪花的间隔

    setInterval(function () {
        var startLeft = Math.random() * documentWidth;
        var endLeft = Math.random() * documentWidth;
        var flakeSize = 3 + 20 * Math.random();
        var durationTime = 4000 + 7000 * Math.random();
        var startOpacity = 0.7 + 0.3 * Math.random();
        var endOpacity = 0.2 + 0.2 * Math.random();
        var cloneFlake = flake.cloneNode(true);

        var colors = ['#fff', '#aee', '#aaf', '#ffa']; // 雪花颜色数组
        var randomColor = colors[Math.floor(Math.random() * colors.length)]; // 随机颜色

        cloneFlake.style.cssText += `
            left: ${startLeft}px;
            opacity: ${startOpacity};
            font-size:${flakeSize}px;
            color:${randomColor};
            top:-25px;
            transition: ${durationTime}ms;
            transform: rotate(${Math.random() * 360}deg);`; // 添加旋转

        document.body.appendChild(cloneFlake);

        setTimeout(function () {
            cloneFlake.style.cssText += `
                left: ${endLeft}px;
                top:${documentHeight}px;
                opacity:${endOpacity};
                transform: rotate(${Math.random() * 360}deg);`;

            setTimeout(function () {
                cloneFlake.remove();
            }, durationTime);
        }, 0);

    }, millisec);
}
snow();

4 个赞

【feat】用字符替换图标,即插即用。同样放入网站全局脚本即可

【fix】页面中部分按钮无法点击的情况

function snow() {

    var flake = document.createElement('div');
    
    flake.innerHTML = '❅';                                 //可选雪花字符 ❄❉❅❆✻✼❇❈❊✥✺
    flake.style.cssText = 'position:absolute;color:#fff;';

    var documentHieght = window.innerHeight;                //获取页面的高度 相当于雪花下落结束时Y轴的位置  
    var documentWidth = window.innerWidth;                  //获取页面的宽度,利用这个数来算出,雪花开始时left的值
    
    var millisec = 10;                                      //定义生成一片雪花的毫秒数
    
    //设置第一个定时器,周期性定时器,每隔一段时间(millisec)生成一片雪花;
    
    setInterval(function () {                               //页面加载之后,定时器就开始工作
        var startLeft = Math.random() * documentWidth;      //随机生成雪花下落 开始 时left的值,相当于开始时X轴的位置
        var endLeft = Math.random() * documentWidth;        //随机生成雪花下落 结束 时left的值,相当于结束时X轴的位置
        var flakeSize = 3 + 20 * Math.random();             //随机生成雪花大小
        var durationTime = 4000 + 7000 * Math.random();     //随机生成雪花下落持续时间
        var startOpacity = 0.7 + 0.3 * Math.random();       //随机生成雪花下落 开始 时的透明度
        var endOpacity = 0.2 + 0.2 * Math.random();         //随机生成雪花下落 结束 时的透明度
        var cloneFlake = flake.cloneNode(true);             //克隆一个雪花模板

        //第一次修改样式,定义克隆出来的雪花的样式
        cloneFlake.style.cssText += `
                        left: ${startLeft}px;
                        opacity: ${startOpacity};
                        font-size:${flakeSize}px;
                        top:-25px;
                            transition:${durationTime}ms;`;

        
        document.body.appendChild(cloneFlake);//拼接到页面中

        //设置第二个定时器,一次性定时器,
        //当第一个定时器生成雪花,并在页面上渲染出来后,修改雪花的样式,让雪花动起来;
        setTimeout(function () {
            //第二次修改样式
            cloneFlake.style.cssText += `
                                left: ${endLeft}px;
                                top:${documentHieght}px;
                                opacity:${endOpacity};`;

            //设置第三个定时器,当雪花落下后,删除雪花。
            setTimeout(function () {
                cloneFlake.remove();
            }, durationTime);
        }, 0);

    }, millisec);
}
snow();

1 个赞

电脑端效果如图


手机端效果:

2 个赞

提供一些常见的圣诞节相关的Unicode字符,您可以使用这些字符来装饰您的网页或者应用:

  1. :christmas_tree: - 圣诞树 (U+1F384)
  2. :gift: - 礼物 (U+1F381)
  3. :santa: - 圣诞老人 (U+1F385)
  4. :snowflake: - 雪花 (U+2744)
  5. :snowman: - 雪人 (U+26C4)
  6. :bell: - 铃铛 (U+1F514)
  7. :star2: - 星星 (U+1F31F)
  8. :cookie: - 曲奇(常用来代表圣诞饼干) (U+1F36A)
  9. :deer: - 鹿(代表圣诞老人的驯鹿) (U+1F98C)
  10. :sled: - 雪橇 (U+1F6F7)

请注意,这些字符的显示可能会因设备和浏览器的不同而有所差异。

减缓了雪花下落速度及移动端雪花数量,优化用户体验;
雪花现在不会被选中;
修正最下端额外边框;
优化显示效果;
添加点击红点就会关闭下雪效果;
一些其它优化;
以及,小小的背景震撼。

++++++++++++++++++++++++++++++++++++++++++++++

<head>
  <style>
    body {
      background-image: linear-gradient(to bottom, #2bc0e4, #eaecc6);
    }
  </style>
</head>

+++++++++++++++++++++++++++++++++++++++++++++

+++++++++++++++++++++++++++++++++++++++++++++

var maxFlakes = 200; // 雪花最大数量
var flakes = []; // 雪花数组
var color = "#fff"; // 雪花颜色
var snowing = true; // 控制雪花下落的变量

// 新增图标数组,包括一个特殊的停止图标
var icons = ["❅", "🎄", "🎁", "🍪", "🌟", "🛑"];

// 检测是否为移动设备
var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
if (isMobile) {
    maxFlakes = 50; // 如果是移动设备,减少雪花数量
}

function snow(density) {
    var flake = document.createElement('div');
    flake.style.cssText = 'position:fixed;color:'+ color +';user-select: none;cursor: pointer;';

    var documentHeight = window.innerHeight;
    var documentWidth = window.innerWidth;
    var millisec = 10;

    var snowInterval = setInterval(function () {
        if (snowing && flakes.length < maxFlakes) {
            var iconIndex = 0; // 默认为雪花
            var specialIconChance = Math.random(); // 特殊图标出现的机会

            // 只有在特殊机会时才选择非雪花图标
            if (specialIconChance < 0.02) { // 减少特殊图标的出现概率
                iconIndex = 1 + Math.floor(Math.random() * (icons.length - 1));
            }

            var startLeft = Math.random() * documentWidth;
            var endLeft = Math.random() * documentWidth;
            var flakeSize = 3 + 20 * Math.random();
            var durationTime = 6000 + 10000 * Math.random();
            var startOpacity = 0.7 + 0.3 * Math.random();
            var endOpacity = 0.2 + 0.2 * Math.random();
            var cloneFlake = flake.cloneNode(true);
            var rotation = 360 * Math.random();

            cloneFlake.innerHTML = icons[iconIndex]; // 使用选择的图标
            cloneFlake.style.cssText += `
                left: ${startLeft}px;
                opacity: ${startOpacity};
                font-size:${flakeSize}px;
                top:-25px;
                transform:rotate(${rotation}deg);
                transition:${durationTime}ms, transform ${durationTime}ms linear;`;

            document.body.appendChild(cloneFlake);
            flakes.push(cloneFlake);

            // 特殊图标(停止符号)的特殊处理
            if (icons[iconIndex] === "🛑") {
                cloneFlake.addEventListener('click', function() {
                    snowing = false; // 停止下雪
                    clearInterval(snowInterval); // 清除定时器
                    flakes.forEach(function(fl) {
                        fl.remove(); // 移除所有雪花
                    });
                    flakes = [];
                });
            } else {
                cloneFlake.addEventListener('click', function() {
                    var index = flakes.indexOf(cloneFlake);
                    if (index > -1) {
                        flakes.splice(index, 1);
                    }
                    cloneFlake.remove();
                });
            }

            setTimeout(function () {
                cloneFlake.style.cssText += `
                    left: ${endLeft}px;
                    top:${documentHeight}px;
                    opacity:${endOpacity};
                    transform:rotate(${rotation + 360}deg);`;

                setTimeout(function () {
                    var index = flakes.indexOf(cloneFlake);
                    if (index > -1) {
                        flakes.splice(index, 1);
                    }
                    cloneFlake.remove();
                }, durationTime);
            }, 0);
        }
    }, millisec / density);
}

snow(1); // 初始化雪花效果

10 个赞

AIchat冬日限定系列主题


更新内容

代码重构

  • [refactor] 重构了雪花飘落的主体代码

新增效果

  • [feat] 新增了10种 冬日/圣诞 图标
  • [feat] 新增了 鼠标/触摸 跟随拖尾效果
  • [feat] 替换 鼠标指针雪花图标
  • [feat] 为下落的图标与鼠标指针添加碰撞交互效果

性能优化

  • [perf] 优化了手机端的显示性能
  • [perf] 优化了雪花飘落角度算法
  • [perf] 支持从现有的对象存储直接调用

BUG修复

  • [fix] 修复了底部异常边框
  • [fix] 修复了生成的图标可以被选中

实机演示

PC端Demo

PC端样式

手机端Demo

手机端样式


使用方式

一. [推荐]直接调用COS对象存储

本人已将成品js脚本上传至COS对象存储(短期),各位站长需要使用的话可以直接进行调用

1.进入管理后台,找到 网站全局脚本 这一栏
2.在后方输入框中输入以下代码:

var script = document.createElement('script');
script.src = 'https://js-snow-1300868563.cos.ap-nanjing.myqcloud.com/snow.js';
document.head.appendChild(script);

3.保存即可

二. 自行本地化

  • 有能力的站长们可以自行将代码放置到js文件中,进行本地化部署
  • 代码会在文末给出

三. [慎用]直接复制代码到网站全局脚本

  • 将文末给出的代码直接复制到 网站全局脚本 这一栏
  • 经个人测试,可以运行,但有可能存在未知问题
  • 若原先全局脚本一栏已经填写过内容,请谨慎使用

源代码

class SnowFall {
    constructor(snow) {
        snow = snow || {};
        this.maxFlake = this.isMobile() ? 15 : (snow.maxFlake || 200);
        this.flakeSize = snow.flakeSize || 15;
        this.canvas = this.createCanvas();
        this.ctx = this.canvas.getContext("2d");
        this.setCanvasSize();
        window.addEventListener('resize', () => {
            cancelAnimationFrame(this.loop);
            this.setCanvasSize();
            this.flakes = this.createFlakes();
            this.start();
        });
        this.flakes = this.createFlakes();
        this.setupMouseInteraction();
    }

    isMobile() {
        return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
    }

    createCanvas() {
        const snowcanvas = document.createElement("canvas");
        snowcanvas.id = "snowfall";
        snowcanvas.width = window.innerWidth;
        snowcanvas.height = window.innerHeight;
        snowcanvas.style.cssText = "position:absolute; top: 0; left: 0; z-index: 1; pointer-events: none;";
        document.body.appendChild(snowcanvas);
        return snowcanvas;
    }

    setCanvasSize() {
        this.canvas.width = window.innerWidth;
        this.canvas.height = window.innerHeight;
    }

    setupMouseInteraction() {
        this.mouseX = 0;
        this.mouseY = 0;
        window.addEventListener('mousemove', (e) => {
            this.mouseX = e.clientX;
            this.mouseY = e.clientY;
        });
    }

    createFlakes() {
        const flakes = [];
        const snowflakeCharacters = ["❄", "❉", "❅", "❆", "✻", "❇", "❈", "❊", "✺", "🎄", "🎁", "🔔", "☃️", "⛄", "❄️", "🎿", "🛷", "🎗️", "🎅"];
        for (let i = 0; i < this.maxFlake; i++) {
            const snowflakeCharacter = snowflakeCharacters[Math.floor(Math.random() * snowflakeCharacters.length)];
            flakes.push(new FlakeMove(this.canvas.width, this.canvas.height, this.flakeSize, snowflakeCharacter));
        }
        return flakes;
    }

    drawSnow() {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.flakes.forEach(flake => {
            flake.update(this.mouseX, this.mouseY);
            flake.render(this.ctx);
        });
        this.loop = requestAnimationFrame(() => this.drawSnow());
    }

    start() {
        this.drawSnow();
    }
}

class FlakeMove {
    constructor(canvasWidth, canvasHeight, flakeSize, character) {
        this.canvasWidth = canvasWidth;
        this.canvasHeight = canvasHeight;
        this.x = Math.floor(Math.random() * canvasWidth);
        this.y = Math.floor(Math.random() * canvasHeight);
        this.size = Math.random() * flakeSize + 2;
        this.maxSize = flakeSize;
        this.velY = Math.random() * 0.8 + 0.3;
        this.velX = Math.random() * 1 - 0.5;
        this.character = character;
    }

    update(mouseX, mouseY) {
        const distance = Math.sqrt(Math.pow(this.x - mouseX, 2) + Math.pow(this.y - mouseY, 2));
        if (distance < 50) {
            const angle = Math.atan2(this.y - mouseY, this.x - mouseX);
            this.velX += Math.cos(angle) * 0.5;
            this.velY += Math.sin(angle) * 0.25;
        }
        this.y += this.velY;
        this.x += this.velX;

        if (this.x >= this.canvasWidth || this.x <= 0 || this.y >= this.canvasHeight || this.y <= 0) {
            this.reset();
        }
    }

    reset() {
        this.x = Math.floor(Math.random() * this.canvasWidth);
        this.y = 0;
        this.size = Math.random() * this.maxSize + 2;
        this.velY = Math.random() * 0.8 + 0.3;
        this.velX = Math.random() * 1 - 0.5;
    }

    render(ctx) {
        ctx.save();
        ctx.font = `${this.size}px Arial`;
        ctx.fillStyle = "white";
        ctx.fillText(this.character, this.x, this.y);
        ctx.restore();
    }
}

const snow = new SnowFall({ maxFlake: 60 });
snow.start();

const style = document.createElement('style');

const css_mouse = 'body, a { cursor: url("data:image/svg+xml;utf8,' +
    '<svg xmlns=\'http://www.w3.org/2000/svg\' height=\'16\' width=\'14\' viewBox=\'0 0 448 512\'>' +
    '<path fill=\'%23ffffff\' d=\'M224 0c13.3 0 24 10.7 24 24V70.1l23-23c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-57 57v76.5l66.2-38.2 20.9-77.8c3.4-12.8 16.6-20.4 29.4-17s20.4 16.6 17 29.4L373 142.2l37.1-21.4c11.5-6.6 26.2-2.7 32.8 8.8s2.7 26.2-8.8 32.8L397 183.8l31.5 8.4c12.8 3.4 20.4 16.6 17 29.4s-16.6 20.4-29.4 17l-77.8-20.9L272 256l66.2 38.2 77.8-20.9c12.8-3.4 26 4.2 29.4 17s-4.2 26-17 29.4L397 328.2l37.1 21.4c11.5 6.6 15.4 21.3 8.8 32.8s-21.3 15.4-32.8 8.8L373 369.8l8.4 31.5c3.4 12.8-4.2 26-17 29.4s-26-4.2-29.4-17l-20.9-77.8L248 297.6v76.5l57 57c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-23-23V488c0 13.3-10.7 24-24 24s-24-10.7-24-24V441.9l-23 23c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l57-57V297.6l-66.2 38.2-20.9 77.8c-3.4 12.8-16.6 20.4-29.4 17s-20.4-16.6-17-29.4L75 369.8 37.9 391.2c-11.5 6.6-26.2 2.7-32.8-8.8s-2.7-26.2 8.8-32.8L51 328.2l-31.5-8.4c-12.8-3.4-20.4-16.6-17-29.4s16.6-20.4 29.4-17l77.8 20.9L176 256l-66.2-38.2L31.9 238.6c-12.8 3.4-26-4.2-29.4-17s4.2-26 17-29.4L51 183.8 13.9 162.4c-11.5-6.6-15.4-21.3-8.8-32.8s21.3-15.4 32.8-8.8L75 142.2l-8.4-31.5c-3.4-12.8 4.2-26 17-29.4s26 4.2 29.4 17l20.9 77.8L200 214.4V137.9L143 81c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l23 23V24c0-13.3 10.7-24 24-24z\'/>' +
    '</svg>") 7 8, pointer; }';

style.appendChild(document.createTextNode(css_mouse));
document.head.appendChild(style);

function fairyDustCursor() {
    const possibleColors = ["#D61C59", "#E7D84B", "#1B8798"];
    const randomCharacters = ["❄", "❉", "❅", "❆", "✻", "❇", "❈", "❊", "✺", "🎄", "🎁", "🔔", "☃️", "⛄", "❄️", "🎿", "🛷", "🎗️", "🎅"];
    let width = window.innerWidth;
    let height = window.innerHeight;
    const cursor = { x: width / 2, y: width / 2 };
    const particles = [];

    function init() {
        bindEvents();
        loop();
    }

    function bindEvents() {
        document.addEventListener('mousemove', onMouseMove);
        document.addEventListener('touchmove', onTouchMove);
        document.addEventListener('touchstart', onTouchMove);
        window.addEventListener('resize', onWindowResize);
    }

    function onWindowResize(e) {
        width = window.innerWidth;
        height = window.innerHeight;
    }

    function onTouchMove(e) {
        if (e.touches.length > 0) {
            for (let i = 0; i < e.touches.length; i++) {
                addParticle(e.touches[i].clientX, e.touches[i].clientY, possibleColors[Math.floor(Math.random() * possibleColors.length)]);
            }
        }
    }

    function onMouseMove(e) {
        cursor.x = e.clientX;
        cursor.y = e.clientY;
        addParticle(cursor.x, cursor.y, possibleColors[Math.floor(Math.random() * possibleColors.length)]);
    }

    function addParticle(x, y, color) {
        const particle = new Particle();
        particle.init(x, y, color);
        particles.push(particle);
    }

    function updateParticles() {
        for (let i = 0; i < particles.length; i++) {
            particles[i].update();
        }
        for (let i = particles.length - 1; i >= 0; i--) {
            if (particles[i].lifeSpan < 0) {
                particles[i].die();
                particles.splice(i, 1);
            }
        }
    }

    function loop() {
        requestAnimationFrame(loop);
        updateParticles();
    }

    function applyProperties(target, properties) {
        for (const key in properties) {
            target.style[key] = properties[key];
        }
        target.style['user-select'] = 'none';
    }

    function Particle() {
        this.character = randomCharacters[Math.floor(Math.random() * randomCharacters.length)];
        this.lifeSpan = 120;
        this.initialStyles = {
            "position": "fixed",
            "top": "0",
            "display": "block",
            "pointerEvents": "none",
            "z-index": "10000000",
            "fontSize": "20px",
            "will-change": "transform"
        };

        this.init = function (x, y, color) {
            this.velocity = {
                x: (Math.random() < 0.5 ? -1 : 1) * (Math.random() / 2),
                y: 1
            };
            this.position = { x: x - 10, y: y - 20 };
            this.initialStyles.color = color;
            this.element = document.createElement('span');
            this.element.innerHTML = this.character;
            applyProperties(this.element, this.initialStyles);
            this.update();
            document.body.appendChild(this.element);
        };

        this.update = function () {
            this.position.x += this.velocity.x;
            this.position.y += this.velocity.y;
            this.lifeSpan--;
            this.element.style.transform = "translate3d(" + this.position.x + "px," + this.position.y + "px,0) scale(" + (this.lifeSpan / 120) + ")";
        };

        this.die = function () {
            this.element.parentNode.removeChild(this.element);
        };
    }

    init();
}

fairyDustCursor();

  • 制作不易,点个赞吧:blush:
9 个赞

非常好 这个最优秀!!

3 个赞
var maxFlakes = 200; // 雪花最大数量
var flakes = []; // 雪花数组
var color = "#fff"; // 雪花颜色
var snowing = true; // 控制雪花下落的变量

// 新增图标数组,包括一个特殊的停止图标
var icons = ["❅", "🎄", "🎁", "🍪", "🌟", "🛑"];

// 检测是否为移动设备
var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
if (isMobile) {
    maxFlakes = 50; // 如果是移动设备,减少雪花数量
}

function snow(density) {
    var flake = document.createElement('div');
    flake.style.cssText = 'position:fixed;color:'+ color +';user-select: none;cursor: pointer;';

    var documentHeight = window.innerHeight;
    var documentWidth = window.innerWidth;
    var millisec = 10;

    var snowInterval = setInterval(function () {
        if (snowing && flakes.length < maxFlakes) {
            var iconIndex = 0; // 默认为雪花
            var specialIconChance = Math.random(); // 特殊图标出现的机会

            // 只有在特殊机会时才选择非雪花图标
            if (specialIconChance < 0.02) { // 减少特殊图标的出现概率
                iconIndex = 1 + Math.floor(Math.random() * (icons.length - 1));
            }

            var startLeft = Math.random() * documentWidth;
            var endLeft = Math.random() * documentWidth;
            var flakeSize = 3 + 20 * Math.random();
            var durationTime = 6000 + 10000 * Math.random();
            var startOpacity = 0.7 + 0.3 * Math.random();
            var endOpacity = 0.2 + 0.2 * Math.random();
            var cloneFlake = flake.cloneNode(true);
            var rotation = 360 * Math.random();

            cloneFlake.innerHTML = icons[iconIndex]; // 使用选择的图标
            cloneFlake.style.cssText += `
                left: ${startLeft}px;
                opacity: ${startOpacity};
                font-size:${flakeSize}px;
                top:-25px;
                transform:rotate(${rotation}deg);
                transition:${durationTime}ms, transform ${durationTime}ms linear;`;

            document.body.appendChild(cloneFlake);
            flakes.push(cloneFlake);

            // 特殊图标(停止符号)的特殊处理
            if (icons[iconIndex] === "🛑") {
                cloneFlake.addEventListener('click', function() {
                    snowing = false; // 停止下雪
                    clearInterval(snowInterval); // 清除定时器
                    flakes.forEach(function(fl) {
                        fl.remove(); // 移除所有雪花
                    });
                    flakes = [];
                });
            } else {
                cloneFlake.addEventListener('click', function() {
                    var index = flakes.indexOf(cloneFlake);
                    if (index > -1) {
                        flakes.splice(index, 1);
                    }
                    cloneFlake.remove();
                });
            }

            setTimeout(function () {
                cloneFlake.style.cssText += `
                    left: ${endLeft}px;
                    top:${documentHeight}px;
                    opacity:${endOpacity};
                    transform:rotate(${rotation + 360}deg);`;

                setTimeout(function () {
                    var index = flakes.indexOf(cloneFlake);
                    if (index > -1) {
                        flakes.splice(index, 1);
                    }
                    cloneFlake.remove();
                }, durationTime);
            }, 0);
        }
    }, millisec / density);
}

snow(1); // 初始化雪花效果

代码的重新补充
点击红点会关闭下雪的效果

1 个赞

AIchat冬日限定系列主题


更新内容

  • [feat] 在前端新增了控制开关

实机效果

PC端效果

PC端样式

手机端效果

手机端样式

使用方式

一. [推荐]直接调用COS对象存储

对象存储中的代码已更新,之前调用对象存储的站长无需更新

1.进入管理后台,找到 网站全局脚本 这一栏
2.在后方输入框中输入以下代码:

var script = document.createElement('script');
script.src = 'https://js-snow-1300868563.cos.ap-nanjing.myqcloud.com/snow.js';
document.head.appendChild(script);

3.保存即可

二. 自行本地化

  • 有能力的站长们可以自行将代码放置到js文件中,进行本地化部署
  • 代码会在文末给出

三. [慎用]直接复制代码到网站全局脚本

  • 将文末给出的代码直接复制到 网站全局脚本 这一栏
  • 经个人测试,可以运行,但有可能存在未知问题
  • 若原先全局脚本一栏已经填写过内容,请谨慎使用

源代码

class SnowFall {
    constructor(snow) {
        snow = snow || {};
        this.isAnimating = false;
        this.maxFlake = this.isMobile() ? 15 : (snow.maxFlake || 200);
        this.flakeSize = snow.flakeSize || 15;
        this.canvas = this.createCanvas();
        this.ctx = this.canvas.getContext("2d");
        this.setCanvasSize();
        window.addEventListener('resize', () => {
            cancelAnimationFrame(this.loop);
            this.setCanvasSize();
            this.flakes = this.createFlakes();
            this.start();
        });
        this.flakes = this.createFlakes();
        this.setupMouseInteraction();
    }

    isMobile() {
        return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
    }

    createCanvas() {
        const snowcanvas = document.createElement("canvas");
        snowcanvas.id = "snowfall";
        snowcanvas.width = window.innerWidth;
        snowcanvas.height = window.innerHeight;
        snowcanvas.style.cssText = "position:absolute; top: 0; left: 0; z-index: 1; pointer-events: none;";
        document.body.appendChild(snowcanvas);
        return snowcanvas;
    }

    setCanvasSize() {
        this.canvas.width = window.innerWidth;
        this.canvas.height = window.innerHeight;
    }

    setupMouseInteraction() {
        this.mouseX = 0;
        this.mouseY = 0;
        window.addEventListener('mousemove', (e) => {
            this.mouseX = e.clientX;
            this.mouseY = e.clientY;
        });
    }

    createFlakes() {
        const flakes = [];
        const snowflakeCharacters = ["❄", "❉", "❅", "❆", "✻", "❇", "❈", "❊", "✺", "🎄", "🎁", "🔔", "☃️", "⛄", "❄️", "🎿", "🛷", "🎗️", "🎅"];
        for (let i = 0; i < this.maxFlake; i++) {
            const snowflakeCharacter = snowflakeCharacters[Math.floor(Math.random() * snowflakeCharacters.length)];
            flakes.push(new FlakeMove(this.canvas.width, this.canvas.height, this.flakeSize, snowflakeCharacter));
        }
        return flakes;
    }

    drawSnow() {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.flakes.forEach(flake => {
            flake.update(this.mouseX, this.mouseY);
            flake.render(this.ctx);
        });
        this.loop = requestAnimationFrame(() => this.drawSnow());
    }

    start() {
        this.drawSnow();
    }

    toggleAnimation() {
        if (this.isAnimating) {
            this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
            cancelAnimationFrame(this.loop);
        } else {
            this.start();
        }
        this.isAnimating = !this.isAnimating;
        
    }
}

class FlakeMove {
    constructor(canvasWidth, canvasHeight, flakeSize, character) {
        this.canvasWidth = canvasWidth;
        this.canvasHeight = canvasHeight;
        this.x = Math.floor(Math.random() * canvasWidth);
        this.y = Math.floor(Math.random() * canvasHeight);
        this.size = Math.random() * flakeSize + 2;
        this.maxSize = flakeSize;
        this.velY = Math.random() * 0.8 + 0.3;
        this.velX = Math.random() * 1 - 0.5;
        this.character = character;
    }

    update(mouseX, mouseY) {
        const distance = Math.sqrt(Math.pow(this.x - mouseX, 2) + Math.pow(this.y - mouseY, 2));
        if (distance < 50) {
            const angle = Math.atan2(this.y - mouseY, this.x - mouseX);
            this.velX += Math.cos(angle) * 0.5;
            this.velY += Math.sin(angle) * 0.25;
        }
        this.y += this.velY;
        this.x += this.velX;

        if (this.x >= this.canvasWidth || this.x <= 0 || this.y >= this.canvasHeight || this.y <= 0) {
            this.reset();
        }
    }

    reset() {
        this.x = Math.floor(Math.random() * this.canvasWidth);
        this.y = 0;
        this.size = Math.random() * this.maxSize + 2;
        this.velY = Math.random() * 0.8 + 0.3;
        this.velX = Math.random() * 1 - 0.5;
    }

    render(ctx) {
        ctx.save();
        ctx.font = `${this.size}px Arial`;
        ctx.fillStyle = "white";
        ctx.fillText(this.character, this.x, this.y);
        ctx.restore();
    }
}

function addButtonIfMissing() {
    const targetSelector = "#app-body > div > div.chat_chat-input-panel__rO72m > div.chat_chat-input-actions__mwYC_";
    const targetElement = document.querySelector(targetSelector);

    if (targetElement) {

        const existingButton = targetElement.querySelector("#toggleButton");

        if (!existingButton) {

            const toggleButton = document.createElement("button");
            toggleButton.id = "toggleButton";
            toggleButton.textContent = "❄️";
            
            toggleButton.style.display = "inline-flex";
            toggleButton.style.borderRadius = "20px";
            toggleButton.style.fontSize = "12px";
            toggleButton.style.backgroundColor = "var(--white)";
            toggleButton.style.color = "var(--black)";
            toggleButton.style.border = "var(--border-in-light)";
            toggleButton.style.padding = "4px 10px";
            toggleButton.style.animation = "chat_slide-in__nvZgA .3s ease";
            toggleButton.style.boxShadow = "var(--card-shadow)";
            toggleButton.style.transition = "all .3s ease";
            toggleButton.style.alignItems = "center";
            toggleButton.style.height = "26px";
            toggleButton.style.width = "var(--icon-width)";

            targetElement.appendChild(toggleButton);

            const snow = new SnowFall({ maxFlake: 60 });

            toggleButton.addEventListener("click", () => {
                snow.toggleAnimation();
            });
        } else {
            console.log("按钮已存在,无需添加");
        }
    } else {
        console.error("未找到指定元素");
    }
}

document.addEventListener("DOMContentLoaded", addButtonIfMissing);
setInterval(addButtonIfMissing, 1000);

const style = document.createElement('style');

const css_mouse = 'body, a { cursor: url("data:image/svg+xml;utf8,' +
    '<svg xmlns=\'http://www.w3.org/2000/svg\' height=\'16\' width=\'14\' viewBox=\'0 0 448 512\'>' +
    '<path fill=\'%23ffffff\' d=\'M224 0c13.3 0 24 10.7 24 24V70.1l23-23c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-57 57v76.5l66.2-38.2 20.9-77.8c3.4-12.8 16.6-20.4 29.4-17s20.4 16.6 17 29.4L373 142.2l37.1-21.4c11.5-6.6 26.2-2.7 32.8 8.8s2.7 26.2-8.8 32.8L397 183.8l31.5 8.4c12.8 3.4 20.4 16.6 17 29.4s-16.6 20.4-29.4 17l-77.8-20.9L272 256l66.2 38.2 77.8-20.9c12.8-3.4 26 4.2 29.4 17s-4.2 26-17 29.4L397 328.2l37.1 21.4c11.5 6.6 15.4 21.3 8.8 32.8s-21.3 15.4-32.8 8.8L373 369.8l8.4 31.5c3.4 12.8-4.2 26-17 29.4s-26-4.2-29.4-17l-20.9-77.8L248 297.6v76.5l57 57c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-23-23V488c0 13.3-10.7 24-24 24s-24-10.7-24-24V441.9l-23 23c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l57-57V297.6l-66.2 38.2-20.9 77.8c-3.4 12.8-16.6 20.4-29.4 17s-20.4-16.6-17-29.4L75 369.8 37.9 391.2c-11.5 6.6-26.2 2.7-32.8-8.8s-2.7-26.2 8.8-32.8L51 328.2l-31.5-8.4c-12.8-3.4-20.4-16.6-17-29.4s16.6-20.4 29.4-17l77.8 20.9L176 256l-66.2-38.2L31.9 238.6c-12.8 3.4-26-4.2-29.4-17s4.2-26 17-29.4L51 183.8 13.9 162.4c-11.5-6.6-15.4-21.3-8.8-32.8s21.3-15.4 32.8-8.8L75 142.2l-8.4-31.5c-3.4-12.8 4.2-26 17-29.4s26 4.2 29.4 17l20.9 77.8L200 214.4V137.9L143 81c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l23 23V24c0-13.3 10.7-24 24-24z\'/>' +
    '</svg>") 7 8, pointer; }';

style.appendChild(document.createTextNode(css_mouse));
document.head.appendChild(style);

function fairyDustCursor() {
    const possibleColors = ["#D61C59", "#E7D84B", "#1B8798"];
    const randomCharacters = ["❄", "❉", "❅", "❆", "✻", "❇", "❈", "❊", "✺", "🎄", "🎁", "🔔", "☃️", "⛄", "❄️", "🎿", "🛷", "🎗️", "🎅"];
    let width = window.innerWidth;
    let height = window.innerHeight;
    const cursor = { x: width / 2, y: width / 2 };
    const particles = [];

    function init() {
        bindEvents();
        loop();
    }

    function bindEvents() {
        document.addEventListener('mousemove', onMouseMove);
        document.addEventListener('touchmove', onTouchMove);
        document.addEventListener('touchstart', onTouchMove);
        window.addEventListener('resize', onWindowResize);
    }

    function onWindowResize(e) {
        width = window.innerWidth;
        height = window.innerHeight;
    }

    function onTouchMove(e) {
        if (e.touches.length > 0) {
            for (let i = 0; i < e.touches.length; i++) {
                addParticle(e.touches[i].clientX, e.touches[i].clientY, possibleColors[Math.floor(Math.random() * possibleColors.length)]);
            }
        }
    }

    function onMouseMove(e) {
        cursor.x = e.clientX;
        cursor.y = e.clientY;
        addParticle(cursor.x, cursor.y, possibleColors[Math.floor(Math.random() * possibleColors.length)]);
    }

    function addParticle(x, y, color) {
        const particle = new Particle();
        particle.init(x, y, color);
        particles.push(particle);
    }

    function updateParticles() {
        for (let i = 0; i < particles.length; i++) {
            particles[i].update();
        }
        for (let i = particles.length - 1; i >= 0; i--) {
            if (particles[i].lifeSpan < 0) {
                particles[i].die();
                particles.splice(i, 1);
            }
        }
    }

    function loop() {
        requestAnimationFrame(loop);
        updateParticles();
    }

    function applyProperties(target, properties) {
        for (const key in properties) {
            target.style[key] = properties[key];
        }
        target.style['user-select'] = 'none';
    }

    function Particle() {
        this.character = randomCharacters[Math.floor(Math.random() * randomCharacters.length)];
        this.lifeSpan = 120;
        this.initialStyles = {
            "position": "fixed",
            "top": "0",
            "display": "block",
            "pointerEvents": "none",
            "z-index": "10000000",
            "fontSize": "20px",
            "will-change": "transform"
        };

        this.init = function (x, y, color) {
            this.velocity = {
                x: (Math.random() < 0.5 ? -1 : 1) * (Math.random() / 2),
                y: 1
            };
            this.position = { x: x - 10, y: y - 20 };
            this.initialStyles.color = color;
            this.element = document.createElement('span');
            this.element.innerHTML = this.character;
            applyProperties(this.element, this.initialStyles);
            this.update();
            document.body.appendChild(this.element);
        };

        this.update = function () {
            this.position.x += this.velocity.x;
            this.position.y += this.velocity.y;
            this.lifeSpan--;
            this.element.style.transform = "translate3d(" + this.position.x + "px," + this.position.y + "px,0) scale(" + (this.lifeSpan / 120) + ")";
        };

        this.die = function () {
            this.element.parentNode.removeChild(this.element);
        };
    }

    init();
}

fairyDustCursor();
3 个赞