实现雪花的效果

首先要在 _config.fluid.ymlcustom_js: 选项中指定实现雪花效果的 js 文件的路径,具体的路径及其设置参考 官方文档的配置指南 | Hexo Fluid 用户手册。我的配置文件如下:

1# 指定自定义 .js 文件路径,支持列表;路径是相对 source 目录,如 /js/custom.js 对应存放目录 source/js/custom.js
2# Specify the path of your custom js file, support list. The path is relative to the source directory, such as `/js/custom.js` corresponding to the directory `source/js/custom.js`
3custom_js: 
4  - /js/custom.js
5  - /js/duration.js
6  - /js/snow.js
7  - /js/firework.js

配置完这个文件之后,在相应路径下创建相关的 js 文件,记得文件名和路径要和 yml 文件中指定的一致,然后在相应的 js 文件中写入如下的代码就可以实现相应的效果:

  1/**
  2 * 雪花飘落效果:
  3 * @Source: https://blog.musnow.top/posts/138502038/index.html
  4 * @Source: https://cnhuazhu.gitee.io/2021/02/24/Hexo%E9%AD%94%E6%94%B9/Hexo%E6%B7%BB%E5%8A%A0%E9%9B%AA%E8%8A%B1%E5%8A%A8%E6%80%81%E6%95%88%E6%9E%9C%E8%83%8C%E6%99%AF/index.html
  5 * @Source: https://cnhuazhu.top/butterfly/2021/02/24/Hexo%E9%AD%94%E6%94%B9/Hexo%E6%B7%BB%E5%8A%A0%E9%9B%AA%E8%8A%B1%E5%8A%A8%E6%80%81%E6%95%88%E6%9E%9C%E8%83%8C%E6%99%AF/
  6 */
  7
  8/** 第一个版本的雪花 */
  9if ((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) {
 10    // 移动端不显示
 11}
 12else {
 13    document.write('<canvas id="snow" style="position:fixed;top:0;left:0;width:100%;height:100%;z-index:100;pointer-events:none"></canvas>');
 14
 15    window && (() => {
 16        let e = {
 17            flakeCount: 50,
 18            minDist: 150,
 19            color: "255, 255, 255",
 20            size: 2,
 21            speed: .5,
 22            opacity: .2,
 23            stepsize: .5
 24        };
 25        const t = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || function (e) {
 26            window.setTimeout(e, 1e3 / 60)
 27        }
 28            ;
 29        window.requestAnimationFrame = t;
 30        const i = document.getElementById("snow"),
 31            n = i.getContext("2d"),
 32            o = e.flakeCount;
 33        let a = -100,
 34            d = -100,
 35            s = [];
 36        i.width = window.innerWidth,
 37            i.height = window.innerHeight;
 38        const h = () => {
 39            n.clearRect(0, 0, i.width, i.height);
 40            const r = e.minDist;
 41            for (let t = 0; t < o; t++) {
 42                let o = s[t];
 43                const h = a,
 44                    w = d,
 45                    m = o.x,
 46                    c = o.y,
 47                    p = Math.sqrt((h - m) * (h - m) + (w - c) * (w - c));
 48                if (p < r) {
 49                    const e = (h - m) / p,
 50                        t = (w - c) / p,
 51                        i = r / (p * p) / 2;
 52                    o.velX -= i * e,
 53                        o.velY -= i * t
 54                } else
 55                    o.velX *= .98,
 56                        o.velY < o.speed && o.speed - o.velY > .01 && (o.velY += .01 * (o.speed - o.velY)),
 57                        o.velX += Math.cos(o.step += .05) * o.stepSize;
 58                n.fillStyle = "rgba(" + e.color + ", " + o.opacity + ")",
 59                    o.y += o.velY,
 60                    o.x += o.velX,
 61                    (o.y >= i.height || o.y <= 0) && l(o),
 62                    (o.x >= i.width || o.x <= 0) && l(o),
 63                    n.beginPath(),
 64                    n.arc(o.x, o.y, o.size, 0, 2 * Math.PI),
 65                    n.fill()
 66            }
 67            t(h)
 68        }
 69            , l = e => {
 70                e.x = Math.floor(Math.random() * i.width),
 71                    e.y = 0,
 72                    e.size = 3 * Math.random() + 2,
 73                    e.speed = 1 * Math.random() + .5,
 74                    e.velY = e.speed,
 75                    e.velX = 0,
 76                    e.opacity = .5 * Math.random() + .3
 77            }
 78            ;
 79        document.addEventListener("mousemove", (e => {
 80            a = e.clientX,
 81                d = e.clientY
 82        }
 83        )),
 84            window.addEventListener("resize", (() => {
 85                i.width = window.innerWidth,
 86                    i.height = window.innerHeight
 87            }
 88            )),
 89            (() => {
 90                for (let t = 0; t < o; t++) {
 91                    const t = Math.floor(Math.random() * i.width)
 92                        , n = Math.floor(Math.random() * i.height)
 93                        , o = 3 * Math.random() + e.size
 94                        , a = 1 * Math.random() + e.speed
 95                        , d = .5 * Math.random() + e.opacity;
 96                    s.push({
 97                        speed: a,
 98                        velX: 0,
 99                        velY: a,
100                        x: t,
101                        y: n,
102                        size: o,
103                        stepSize: Math.random() / 30 * e.stepsize,
104                        step: 0,
105                        angle: 180,
106                        opacity: d
107                    })
108                }
109                h()
110            }
111            )()
112    }
113    )();
114}
115
116// 移动端不显示
117if (!(navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) {
118    (function ($) {
119        $.fn.snow = function (options) {
120            var $flake = $('<div id="snowbox" />').css({ 'position': 'absolute', 'z-index': '9999', 'top': '-50px' }).html('&#10052;'),
121                documentheight = $(document).height(),
122                documentwidth = $(document).width(),
123                defaults = {
124                    minsize: 10,
125                    maxsize: 20,
126                    newon: 1000,
127                    flakecolor: "#afdaef" /* 此处可以定义雪花颜色,若要白色可以改为#ffffff */
128                },
129                options = $.extend({}, defaults, options);
130            var interval = setInterval(function () {
131                var startpositionleft = Math.random() * documentwidth - 100,
132                    startopacity = 0.5 + Math.random(),
133                    sizeflake = options.minsize + Math.random() * options.maxsize,
134                    endpositiontop = documentheight - 200,
135                    endpositionleft = startpositionleft - 500 + Math.random() * 500,
136                    durationfall = documentheight * 10 + Math.random() * 5000;
137                $flake.clone().appendTo('body').css({
138                    left: startpositionleft,
139                    opacity: startopacity,
140                    'font-size': sizeflake,
141                    color: options.flakecolor
142                }).animate({
143                    top: endpositiontop,
144                    left: endpositionleft,
145                    opacity: 0.2
146                }, durationfall, 'linear', function () {
147                    $(this).remove();
148                });
149            }, options.newon);
150        };
151    })(jQuery);
152
153    $(function () {
154        $.fn.snow({
155            minsize: 5, /* 定义雪花最小尺寸 */
156            maxsize: 25,/* 定义雪花最大尺寸 */
157            newon: 800  /* 定义密集程度,数字越小越密集 */
158        });
159    });
160}

顺便把另外一个版本的雪花也贴上来:

  1/** 另一个版本的雪花 */
  2function snowFall(snow) {
  3    /* 可配置属性 */
  4    snow = snow || {};
  5    this.maxFlake = snow.maxFlake || 600; /* 最多片数 */
  6    this.flakeSize = snow.flakeSize || 10; /* 雪花形状 */
  7    this.fallSpeed = snow.fallSpeed || 1; /* 坠落速度 */
  8}
  9
 10/* 兼容写法 */
 11requestAnimationFrame = window.requestAnimationFrame ||
 12    window.mozRequestAnimationFrame ||
 13    window.webkitRequestAnimationFrame ||
 14    window.msRequestAnimationFrame ||
 15    window.oRequestAnimationFrame ||
 16    function (callback) {
 17        setTimeout(callback, 1000 / 60);
 18    };
 19
 20cancelAnimationFrame = window.cancelAnimationFrame ||
 21    window.mozCancelAnimationFrame ||
 22    window.webkitCancelAnimationFrame ||
 23    window.msCancelAnimationFrame ||
 24    window.oCancelAnimationFrame;
 25
 26/* 开始下雪 */
 27snowFall.prototype.start = function () {
 28    /* 创建画布 */
 29    snowCanvas.apply(this);
 30    /* 创建雪花形状 */
 31    createFlakes.apply(this);
 32    /* 画雪 */
 33    drawSnow.apply(this);
 34}
 35
 36/* 创建画布 */
 37function snowCanvas() {
 38    /* 添加Dom结点 */
 39    var snowcanvas = document.createElement("canvas");
 40    snowcanvas.id = "snowfall";
 41    snowcanvas.width = window.innerWidth;
 42    snowcanvas.height = document.documentElement.scrollHeight; // 使用scrollHeight获取完整文档高度
 43    snowcanvas.setAttribute("style", "position:fixed; top: 0; left: 0; z-index: 1; pointer-events: none;");
 44    document.body.appendChild(snowcanvas);
 45    this.canvas = snowcanvas;
 46    this.ctx = snowcanvas.getContext("2d");
 47    /* 窗口大小改变的处理 */
 48    window.onresize = function () {
 49        snowcanvas.width = window.innerWidth;
 50        snowcanvas.height = document.documentElement.scrollHeight;
 51    }
 52}
 53
 54/* 雪运动对象 */
 55function flakeMove(canvasWidth, canvasHeight, flakeSize, fallSpeed) {
 56    this.x = Math.floor(Math.random() * canvasWidth); /* x坐标 */
 57    this.y = Math.floor(Math.random() * canvasHeight); /* y坐标 */
 58    this.size = Math.random() * flakeSize + 2; /* 形状 */
 59    this.maxSize = flakeSize; /* 最大形状 */
 60    this.speed = Math.random() * 1 + fallSpeed; /* 坠落速度 */
 61    this.fallSpeed = fallSpeed; /* 坠落速度 */
 62    this.velY = this.speed; /* Y方向速度 */
 63    this.velX = 0; /* X方向速度 */
 64    this.stepSize = Math.random() / 30; /* 步长 */
 65    this.step = 0 /* 步数 */
 66}
 67
 68flakeMove.prototype.update = function () {
 69    var x = this.x,
 70        y = this.y;
 71    /* 左右摆动(余弦) */
 72    this.velX *= 0.98;
 73    if (this.velY <= this.speed) {
 74        this.velY = this.speed
 75    }
 76    this.velX += Math.cos(this.step += .05) * this.stepSize;
 77
 78    this.y += this.velY;
 79    this.x += this.velX;
 80    /* 飞出边界的处理 */
 81    if (this.x >= canvas.width || this.x <= 0 || this.y >= canvas.height || this.y <= 0) {
 82        this.reset(canvas.width, canvas.height)
 83    }
 84};
 85
 86/* 飞出边界-放置最顶端继续坠落 */
 87flakeMove.prototype.reset = function (width, height) {
 88    this.x = Math.floor(Math.random() * width);
 89    this.y = 0;
 90    this.size = Math.random() * this.maxSize + 2;
 91    this.speed = Math.random() * 1 + this.fallSpeed;
 92    this.velY = this.speed;
 93    this.velX = 0;
 94};
 95
 96// 渲染雪花-随机形状(此处可修改雪花颜色!!!)
 97flakeMove.prototype.render = function (ctx) {
 98    var snowFlake = ctx.createRadialGradient(this.x, this.y - window.scrollY, 0, this.x, this.y - window.scrollY, this.size);
 99    snowFlake.addColorStop(0, "rgba(255, 255, 255, 0.9)"); /* 此处是雪花颜色,默认是白色 */
100    snowFlake.addColorStop(.5, "rgba(255, 255, 255, 0.5)"); /* 若要改为其他颜色,请自行查 */
101    snowFlake.addColorStop(1, "rgba(255, 255, 255, 0)"); /* 找16进制的RGB 颜色代码。 */
102    ctx.save();
103    ctx.fillStyle = snowFlake;
104    ctx.beginPath();
105    ctx.arc(this.x, this.y - window.scrollY, this.size, 0, Math.PI * 2);
106    ctx.fill();
107    ctx.restore();
108};
109
110/* 创建雪花-定义形状 */
111function createFlakes() {
112    var maxFlake = this.maxFlake,
113        flakes = this.flakes = [],
114        canvas = this.canvas;
115    for (var i = 0; i < maxFlake; i++) {
116        flakes.push(new flakeMove(canvas.width, canvas.height, this.flakeSize, this.fallSpeed))
117    }
118}
119
120/* 画雪 */
121function drawSnow() {
122    var maxFlake = this.maxFlake,
123        flakes = this.flakes;
124    ctx = this.ctx, canvas = this.canvas, that = this;
125    /* 清空雪花 */
126    ctx.clearRect(0, 0, canvas.width, canvas.height);
127    for (var e = 0; e < maxFlake; e++) {
128        flakes[e].update();
129        flakes[e].render(ctx);
130    }
131    /*  一帧一帧的画 */
132    this.loop = requestAnimationFrame(function () {
133        drawSnow.apply(that);
134    });
135}
136
137/** 只有一种雪花就取消注释下面这段代码,然后注释掉后面的新雪花的代码 */
138/* 调用及控制方法 */
139// var snow = new snowfall({
140//     maxflake: 60
141// });
142// snow.start();
143
144/** 新雪花代码 */
145(function ($) {
146    $.fn.snow = function (options) {
147        var $flake = $('<div class="snowflake" />').css({
148            'position': 'absolute',
149            'z-index': '9999',
150            'top': '-50px'
151        }).html('&#10052;'),
152            documentHeight = $(document).height(),
153            documentWidth = $(document).width(),
154            defaults = {
155                minSize: 10,
156                maxSize: 20,
157                newOn: 1000,
158                flakeColor: "#AFDAEF" /* 此处可以定义雪花颜色,若要白色可以改为#FFFFFF */
159            },
160            options = $.extend({}, defaults, options);
161
162        var interval = setInterval(function () {
163            var startPositionLeft = Math.random() * documentWidth - 100,
164                startOpacity = 0.5 + Math.random(),
165                sizeFlake = options.minSize + Math.random() * options.maxSize,
166                endPositionTop = documentHeight - 200,
167                endPositionLeft = startPositionLeft - 500 + Math.random() * 500,
168                durationFall = documentHeight * 10 + Math.random() * 5000;
169
170            $flake.clone().appendTo('body').css({
171                left: startPositionLeft,
172                opacity: startOpacity,
173                'font-size': sizeFlake,
174                color: options.flakeColor
175            }).animate({
176                top: endPositionTop,
177                left: endPositionLeft,
178                opacity: 0.2
179            }, durationFall, 'linear', function () {
180                $(this).remove();
181            });
182        }, options.newOn);
183    };
184})(jQuery);
185
186$(function () {
187    $.fn.snow({
188        minSize: 5, /* 定义雪花最小尺寸 */
189        maxSize: 25, /* 定义雪花最大尺寸 */
190        newOn: 800 /* 定义密集程度,数字越小越密集 */
191    });
192
193    // 调用新的雪花效果
194    var snowflake = new snowFall({
195        maxFlake: 30
196    });
197    snowflake.start();
198});

实现鼠标点击的烟花效果

加入 js 代码的步骤和前面的实现雪花的步骤类似,这里就不再赘述,下面直接给出实现的代码:

  1/** 鼠标点击放礼花
  2 * @Source: https://cnwjy.site/2022/01/04/Hexo-Fluid%E7%BE%8E%E5%8C%96/
  3 */ 
  4class Circle {
  5    constructor({ origin, speed, color, angle, context }) {
  6      this.origin = origin
  7      this.position = { ...this.origin }
  8      this.color = color
  9      this.speed = speed
 10      this.angle = angle
 11      this.context = context
 12      this.renderCount = 0
 13    }
 14  
 15    draw() {
 16      this.context.fillStyle = this.color
 17      this.context.beginPath()
 18      this.context.arc(this.position.x, this.position.y, 2, 0, Math.PI * 2)
 19      this.context.fill()
 20    }
 21  
 22    move() {
 23      this.position.x = (Math.sin(this.angle) * this.speed) + this.position.x
 24      this.position.y = (Math.cos(this.angle) * this.speed) + this.position.y + (this.renderCount * 0.3)
 25      this.renderCount++
 26    }
 27  }
 28  
 29  class Boom {
 30    constructor ({ origin, context, circleCount = 16, area }) {
 31      this.origin = origin
 32      this.context = context
 33      this.circleCount = circleCount
 34      this.area = area
 35      this.stop = false
 36      this.circles = []
 37    }
 38  
 39    randomArray(range) {
 40      const length = range.length
 41      const randomIndex = Math.floor(length * Math.random())
 42      return range[randomIndex]
 43    }
 44  
 45    randomColor() {
 46      const range = ['8', '9', 'A', 'B', 'C', 'D', 'E', 'F']
 47      return '#' + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) + this.randomArray(range) + this.randomArray(range)
 48    }
 49  
 50    randomRange(start, end) {
 51      return (end - start) * Math.random() + start
 52    }
 53  
 54    init() {
 55      for(let i = 0; i < this.circleCount; i++) {
 56        const circle = new Circle({
 57          context: this.context,
 58          origin: this.origin,
 59          color: this.randomColor(),
 60          angle: this.randomRange(Math.PI - 1, Math.PI + 1),
 61          speed: this.randomRange(1, 6)
 62        })
 63        this.circles.push(circle)
 64      }
 65    }
 66  
 67    move() {
 68      this.circles.forEach((circle, index) => {
 69        if (circle.position.x > this.area.width || circle.position.y > this.area.height) {
 70          return this.circles.splice(index, 1)
 71        }
 72        circle.move()
 73      })
 74      if (this.circles.length == 0) {
 75        this.stop = true
 76      }
 77    }
 78  
 79    draw() {
 80      this.circles.forEach(circle => circle.draw())
 81    }
 82  }
 83  
 84  class CursorSpecialEffects {
 85    constructor() {
 86      this.computerCanvas = document.createElement('canvas')
 87      this.renderCanvas = document.createElement('canvas')
 88  
 89      this.computerContext = this.computerCanvas.getContext('2d')
 90      this.renderContext = this.renderCanvas.getContext('2d')
 91  
 92      this.globalWidth = window.innerWidth
 93      this.globalHeight = window.innerHeight
 94  
 95      this.booms = []
 96      this.running = false
 97    }
 98  
 99    handleMouseDown(e) {
100      const boom = new Boom({
101        origin: { x: e.clientX, y: e.clientY },
102        context: this.computerContext,
103        area: {
104          width: this.globalWidth,
105          height: this.globalHeight
106        }
107      })
108      boom.init()
109      this.booms.push(boom)
110      this.running || this.run()
111    }
112  
113    handlePageHide() {
114      this.booms = []
115      this.running = false
116    }
117  
118    init() {
119      const style = this.renderCanvas.style
120      style.position = 'fixed'
121      style.top = style.left = 0
122      style.zIndex = '999999999999999999999999999999999999999999'
123      style.pointerEvents = 'none'
124  
125      style.width = this.renderCanvas.width = this.computerCanvas.width = this.globalWidth
126      style.height = this.renderCanvas.height = this.computerCanvas.height = this.globalHeight
127  
128      document.body.append(this.renderCanvas)
129  
130      window.addEventListener('mousedown', this.handleMouseDown.bind(this))
131      window.addEventListener('pagehide', this.handlePageHide.bind(this))
132    }
133  
134    run() {
135      this.running = true
136      if (this.booms.length == 0) {
137        return this.running = false
138      }
139  
140      requestAnimationFrame(this.run.bind(this))
141  
142      this.computerContext.clearRect(0, 0, this.globalWidth, this.globalHeight)
143      this.renderContext.clearRect(0, 0, this.globalWidth, this.globalHeight)
144  
145      this.booms.forEach((boom, index) => {
146        if (boom.stop) {
147          return this.booms.splice(index, 1)
148        }
149        boom.move()
150        boom.draw()
151      })
152      this.renderContext.drawImage(this.computerCanvas, 0, 0, this.globalWidth, this.globalHeight)
153    }
154  }
155  
156  const cursorSpecialEffects = new CursorSpecialEffects()
157  cursorSpecialEffects.init()

实现浏览器搞笑标题效果

下面的代码可以实现当前页面不在和在博客时显示不同的浏览器标题的效果,具体修改到博客中与上面的操作类似,不再赘述。

 1/**
 2 * 浏览器搞笑标题:@Source:https://asteri5m.gitee.io/archives/Fluid%E9%AD%94%E6%94%B9%E7%AC%94%E8%AE%B0.html
 3 */
 4var OriginTitle = document.title;
 5var titleTime;
 6document.addEventListener('visibilitychange', function() {
 7	if (document.hidden) {
 8		$('[rel="icon"]').attr('href', "/funny.ico");
 9		document.title = '╭(°A°`)╮ 页面崩溃啦 ~';
10		clearTimeout(titleTime);
11	} else {
12		$('[rel="icon"]').attr('href', "/img/newtubiao.png");
13		document.title = '(ฅ>ω<*ฅ) 噫又好啦 ~' + OriginTitle;
14		titleTime = setTimeout(function() {
15			document.title = OriginTitle;
16		}, 2000);
17	}
18});