网站搭建

同款博客搭建

声明:本篇文章是介绍安知鱼博客网站的搭建及其魔改,对待 0 基础的朋友比较不友好,但你仍然可以凭借好的学习能力和借助 B 站、AI、百度等完成个人网站的搭建!如有不懂可在文章下方评论区评论,我会尽力解答……

1. hexo 和 安知鱼主题的安装

前提必备技能:GitHub 仓库的基本使用以及 VS Code 的基本使用!

环境与工具准备(最好可以稳定连接 GitHub)

  • Git - 下载

  • Node.js — Download Node.js®(这里选用 v18.17.122.16.0 版本兼容性较好,请确保勾选 Add to PATH 选项(默认已勾选))

    • 安装完成后,检查是否安装成功。在键盘按下 win + R 键,输入 CMD,然后回车,打开 CMD 窗口,执行 node -v 命令,看到版本信息,则说明安装成功。
  • 文本编辑器:VS Code 下载(推荐) 或者 Trae - AI 原生 IDE 下载(看个人,选任意一个或者都选都可以)

  • 安装 Hexo

    • 在终端(CMD)中输入:

      1
      npm install -g hexo-cli
    • 安装完后输入 hexo -v 验证是否安装成功。

  • 在 GitHub 上创建一个仓库,注意名称格式必须是 你的用户名.github.io

  • 初始化 Hexo 项目

    • 找到一个存放博客的地方(文件夹要是空的),输入命令:

      1
      hexo init MyBlog	# MyBlog是项目名,也是文件夹的名称,这里自己自定义即可
    • 完成后,在这个文件夹里就可以看到下面的内容:

      image-20250607114937613

  • 安装 hexo-deployer-git(将静态博客挂载到 GitHub Pages,直观来说就是将本地博客状态推送至 GitHub)

    • 安装

      1
      npm install hexo-deployer-git --save
    • 修改 _config.yml 文件:修改最后一行的配置,将 repository 修改为你自己的 github 项目地址即可,还有分支要改为 main 代表主分支(注意缩进)。

      1
      2
      3
      4
      deploy:
      type: git
      repository: git@github.com:你的GitHub用户名.github.io.git # 也可以https://github.com/你的用户名.github.io.git
      branch: main
    • 推送命令(注意推送前最好使用先使用 hexo cl;hexo g;hexo s ,确保最新且没问题):

      1
      hexo deployer		# 我们常用简写 hexo d

      当我们完成第一次推送(hexo d)后,正常来说就可以通过 GitHub 自动分配的 你的 GitHub 用户名 github.io 的这个域名进行网站的访问了(可能存在几分钟的延迟)。


主题安装和博客搭建(安知鱼主题官方文档

下面使用我们的文本编辑器(VS Code)打开这个 MyBlog 文件夹开始安装主题,进行博客搭建:全部按照 安知鱼主题官方文档 进行操作即可(已经非常详细了!)

image-20250607120237873

还有启用主题:找到 Hexo 根目录下的 config.yml, 找到以下配置项,把主题改为 anzhiyu

1
2
3
4
# Extensions
## Plugins: https://hexo.io/plugins/
## Themes: https://hexo.io/themes/
theme: anzhiyu

不要忘了安装 pug 和 stylus 渲染插件!这里是不管如何最好两个命令全部执行一下:

image-20250607120552213

后面的页面配置完全参考 安知鱼主题官方文档 后面的部分即可:

image-20250607122857105

文章发布

如果你看到这里,相信你的网站已经基本建设成功,恭喜了!下面我们讲解如何发布文章:在 Hexo 根目录下找到 source/_posts,将自己的 markdown 文件放到这里,执行 hexo cl;hexo g;hexo s 就可以看到文章成功发布了,最后执行 hexo d 进行推送就大功告成了。

如果你不知道/不会 markdown 语法也有必要了解/学一下,基本会用差不多只需要 10 分钟上下,还是蛮简单的,这里推荐使用 Typora(附免费图床教程)进行编辑和学习。


2. vercel 部署

这里就有一点点复杂了,建议要有一些域名主机记录、类型和域名操作的基础,下面三两句话可能讲的不是很清楚,有问题请自行 B 站、AI。

Vercel 简介:vercel 是一个代码托管平台,它能够托管你的静态 html 界面,甚至能够托管你的 node.js 与 Python 服务端脚本,是 不想买服务器的懒人的福音

  1. 首先需要一个 Vercel 账号,这里 推荐用 GitHub 账户关联,这样你就可以在 vercel 中直接托管你的 GitHub 库中的项目了,实现开发部署一步到位。
  2. 当你用你的 Github 账户关联并绑定手机号登录之后,点击右上角的 Add New Project 创建新的项目,之后导入选项那里选择 Continue with Github,这时候应该能看到你 Github 账号的仓库,选择你刚刚部署成功的存储静态博客的仓库 <username>.github.io 右边的 Import 选项,表示你要导入该仓库。
  3. 起一个只能有字母、数字或者或者连字符的项目名称,然后其他默认,点击 Deploy,等待一分钟即可部署成功,部署成功后电极 Continue to Dashboard 跳转到控制面板,下图所示就是控制面板,看到就代表成功部署成功了。
  4. 绑定自定义域名(可选):如果不想付费也有免费的域名 GitHub 源点此开始申请免费域名
    1. 点击 Vercel 控制面板右上角的 View Domains 查看当前的域,直接输入你新买的(一级)域名即可,例如 demo123.com,他会推荐你将 demo123.com 重定向至 www.demo123.com,点 ADD 即可,然后他会提示你添加两条解析记录,一个是 @ 开头的和 CNAME 开头的,添加记录的方法和二级域名一致。
    2. 在购买域名厂商的域名后台解析记录里面添加如下记录,其中记录类型对应 Type,主机记录对应 Name,记录值对应 Value,其他的设置默认即可。
    3. 回到 Vercel 刚刚查看域名的地方,如果操作没问题,应该会显示域名配置成功的提示,此时就可以通过自定义域名来访问我们搭建的网站了。
    4. 当你有了新的域名之后,需要 [BlogRoot]\_config.yml 文件中的 url 配置项为自己的新域名,这样博客的文章链接才会正确生成。

3. 博客的魔改(魔改容易出错,建议魔改前备份!!)

移除首页头图的阴影

找到 博客目录/themes/anzhiyu/source/css/_extra/home_top/top_group_banner.css,大约在第 109 行 删除 box-shadow: 0 -109px 133px -9px #000000 inset; 或修改后面的 16 位色值大概和你图片相匹配即可

image-20250607191210974

效果展示:地址栏会变成“自定义”的固定链接

image-20250607124549041

  1. 安装

    1
    npm install hexo-abbrlink --save
  2. 修改 hexo config.yml 文件中的永久链接为

    1
    2
    3
    4
    5
    permalink: posts/:abbrlink.html   #文章结尾带 .html(推荐,下面这种会有一点问题,只会影响多层级分类跳转的问题,不影响网站的功能)

    # 或者
    permalink: posts/:abbrlink/ #文章结尾不带 .html
    # 前面posts也可自定义修改
  3. 做到上面一步,其实就已经可以了。下面是可选内容:将以下内容复制到 hexo config.yml 的最底下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    # 一个 Hexo 插件,用于根据帖子正面的标题和数据生成静态帖子链接。
    # https://github.com/rozbo/hexo-abbrlink
    # abbrlink config
    abbrlink:
    alg: crc16 #support crc16(default) and crc32
    rep: hex #support dec(default) and hex
    drafts: false #(true)处理草稿,(false)不处理草稿。false(默认值)
    # 从目录树生成类别
    # 深度:要生成的目录树的最大深度应大于0
    auto_category:
    enable: true #true(default)
    depth: #3(default)
    over_write: false
    auto_title: false #启用自动标题,可以按路径自动填充标题
    auto_date: false #启用自动日期,它可以自动填写日期的时间今天
    force: false #启用强制模式,在这种模式下,插件将忽略缓存,并为每个帖子计算abbrlink,即使它已经有了abbrlink。

添加一个统计页面

  1. 新建一个分类:根目录下执行以下代码:

    1
    hexo new page charts
  2. 前往博客根目录下 source/charts/index.md,直接替换全部内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ---
    title: 统计
    type: "charts"
    comments: false
    aside: false
    ---

    <script src="https://cdn.bootcdn.net/ajax/libs/echarts/4.9.0-rc.1/echarts.min.js"></script>

    <!-- 文章发布时间统计图 --> <!-- 2024-09是从20249月开始计算 -->
    <div id="posts-chart" data-start="2024-09" style="border-radius: 8px; height: 300px; padding: 10px;"></div>
    <!-- 文章标签统计图 --> <!-- data-length="10" 是显示的标签数量 -->
    <div id="tags-chart" data-length="10" style="border-radius: 8px; height: 300px; padding: 10px;"></div>
    <!-- 文章分类统计图 -->
    <div id="categories-chart" data-parent="true" style="border-radius: 8px; height: 300px; padding: 10px;"></div>
  3. 主题目录 /themes/anzhiyu/scripts/helpers 下添加一个 charts.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    // charts.js
    const cheerio = require('cheerio')
    const moment = require('moment')

    hexo.extend.filter.register('after_render:html', function (locals) {
    const $ = cheerio.load(locals)
    const post = $('#posts-chart')
    const tag = $('#tags-chart')
    const category = $('#categories-chart')
    const htmlEncode = false

    if (post.length > 0 || tag.length > 0 || category.length > 0) {
    if (post.length > 0 && $('#postsChart').length === 0) {
    if (post.attr('data-encode') === 'true') htmlEncode = true
    post.after(postsChart(post.attr('data-start')))
    }
    if (tag.length > 0 && $('#tagsChart').length === 0) {
    if (tag.attr('data-encode') === 'true') htmlEncode = true
    tag.after(tagsChart(tag.attr('data-length')))
    }
    if (category.length > 0 && $('#categoriesChart').length === 0) {
    if (category.attr('data-encode') === 'true') htmlEncode = true
    category.after(categoriesChart(category.attr('data-parent')))
    }

    if (htmlEncode) {
    return $.root().html().replace(/&amp;#/g, '&#')
    } else {
    return $.root().html()
    }
    } else {
    return locals
    }
    }, 15)

    function postsChart (startMonth) {
    const startDate = moment(startMonth || '2020-01')
    const endDate = moment()

    const monthMap = new Map()
    const dayTime = 3600 * 24 * 1000
    for (let time = startDate; time <= endDate; time += dayTime) {
    const month = moment(time).format('YYYY-MM')
    if (!monthMap.has(month)) {
    monthMap.set(month, 0)
    }
    }
    hexo.locals.get('posts').forEach(function (post) {
    const month = post.date.format('YYYY-MM')
    if (monthMap.has(month)) {
    monthMap.set(month, monthMap.get(month) + 1)
    }
    })
    const monthArr = JSON.stringify([...monthMap.keys()])
    const monthValueArr = JSON.stringify([...monthMap.values()])

    return `
    <script id="postsChart">
    var color = document.documentElement.getAttribute('data-theme') === 'light' ? '#4c4948' : 'rgba(255,255,255,0.7)'
    var postsChart = echarts.init(document.getElementById('posts-chart'), 'light');
    var postsOption = {
    title: {
    text: '文章发布统计图',
    x: 'center',
    textStyle: {
    color: color
    }
    },
    tooltip: {
    trigger: 'axis'
    },
    xAxis: {
    name: '日期',
    type: 'category',
    boundaryGap: false,
    nameTextStyle: {
    color: color
    },
    axisTick: {
    show: false
    },
    axisLabel: {
    show: true,
    color: color
    },
    axisLine: {
    show: true,
    lineStyle: {
    color: color
    }
    },
    data: ${monthArr}
    },
    yAxis: {
    name: '文章篇数',
    type: 'value',
    nameTextStyle: {
    color: color
    },
    splitLine: {
    show: false
    },
    axisTick: {
    show: false
    },
    axisLabel: {
    show: true,
    color: color
    },
    axisLine: {
    show: true,
    lineStyle: {
    color: color
    }
    }
    },
    series: [{
    name: '文章篇数',
    type: 'line',
    smooth: true,
    lineStyle: {
    width: 0
    },
    showSymbol: false,
    itemStyle: {
    opacity: 1,
    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
    offset: 0,
    color: 'rgba(128, 255, 165)'
    },
    {
    offset: 1,
    color: 'rgba(1, 191, 236)'
    }])
    },
    areaStyle: {
    opacity: 1,
    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
    offset: 0,
    color: 'rgba(128, 255, 165)'
    }, {
    offset: 1,
    color: 'rgba(1, 191, 236)'
    }])
    },
    data: ${monthValueArr},
    markLine: {
    data: [{
    name: '平均值',
    type: 'average',
    label: {
    color: color
    }
    }]
    }
    }]
    };
    postsChart.setOption(postsOption);
    window.addEventListener('resize', () => {
    postsChart.resize();
    });
    postsChart.on('click', 'series', (event) => {
    if (event.componentType === 'series') window.location.href = '/archives/' + event.name.replace('-', '/');
    });
    </script>`
    }

    function tagsChart (len) {
    const tagArr = []
    hexo.locals.get('tags').map(function (tag) {
    tagArr.push({ name: tag.name, value: tag.length, path: tag.path })
    })
    tagArr.sort((a, b) => { return b.value - a.value })

    const dataLength = Math.min(tagArr.length, len) || tagArr.length
    const tagNameArr = []
    for (let i = 0; i < dataLength; i++) {
    tagNameArr.push(tagArr[i].name)
    }
    const tagNameArrJson = JSON.stringify(tagNameArr)
    const tagArrJson = JSON.stringify(tagArr)

    return `
    <script id="tagsChart">
    var color = document.documentElement.getAttribute('data-theme') === 'light' ? '#4c4948' : 'rgba(255,255,255,0.7)'
    var tagsChart = echarts.init(document.getElementById('tags-chart'), 'light');
    var tagsOption = {
    title: {
    text: 'Top ${dataLength} 标签统计图',
    x: 'center',
    textStyle: {
    color: color
    }
    },
    tooltip: {},
    xAxis: {
    name: '标签',
    type: 'category',
    nameTextStyle: {
    color: color
    },
    axisTick: {
    show: false
    },
    axisLabel: {
    show: true,
    color: color,
    interval: 0
    },
    axisLine: {
    show: true,
    lineStyle: {
    color: color
    }
    },
    data: ${tagNameArrJson}
    },
    yAxis: {
    name: '文章篇数',
    type: 'value',
    splitLine: {
    show: false
    },
    nameTextStyle: {
    color: color
    },
    axisTick: {
    show: false
    },
    axisLabel: {
    show: true,
    color: color
    },
    axisLine: {
    show: true,
    lineStyle: {
    color: color
    }
    }
    },
    series: [{
    name: '文章篇数',
    type: 'bar',
    data: ${tagArrJson},
    itemStyle: {
    borderRadius: [5, 5, 0, 0],
    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
    offset: 0,
    color: 'rgba(128, 255, 165)'
    },
    {
    offset: 1,
    color: 'rgba(1, 191, 236)'
    }])
    },
    emphasis: {
    itemStyle: {
    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
    offset: 0,
    color: 'rgba(128, 255, 195)'
    },
    {
    offset: 1,
    color: 'rgba(1, 211, 255)'
    }])
    }
    },
    markLine: {
    data: [{
    name: '平均值',
    type: 'average',
    label: {
    color: color
    }
    }]
    }
    }]
    };
    tagsChart.setOption(tagsOption);
    window.addEventListener('resize', () => {
    tagsChart.resize();
    });
    tagsChart.on('click', 'series', (event) => {
    if(event.data.path) window.location.href = '/' + event.data.path;
    });
    </script>`
    }

    function categoriesChart (dataParent) {
    const categoryArr = []
    let categoryParentFlag = false
    hexo.locals.get('categories').map(function (category) {
    if (category.parent) categoryParentFlag = true
    categoryArr.push({
    name: category.name,
    value: category.length,
    path: category.path,
    id: category._id,
    parentId: category.parent || '0'
    })
    })
    categoryParentFlag = categoryParentFlag && dataParent === 'true'
    categoryArr.sort((a, b) => { return b.value - a.value })
    function translateListToTree (data, parent) {
    let tree = []
    let temp
    data.forEach((item, index) => {
    if (data[index].parentId == parent) {
    let obj = data[index];
    temp = translateListToTree(data, data[index].id);
    if (temp.length > 0) {
    obj.children = temp
    }
    if (tree.indexOf())
    tree.push(obj)
    }
    })
    return tree
    }
    const categoryNameJson = JSON.stringify(categoryArr.map(function (category) { return category.name }))
    const categoryArrJson = JSON.stringify(categoryArr)
    const categoryArrParentJson = JSON.stringify(translateListToTree(categoryArr, '0'))

    return `
    <script id="categoriesChart">
    var color = document.documentElement.getAttribute('data-theme') === 'light' ? '#4c4948' : 'rgba(255,255,255,0.7)'
    var categoriesChart = echarts.init(document.getElementById('categories-chart'), 'light');
    var categoryParentFlag = ${categoryParentFlag}
    var categoriesOption = {
    title: {
    text: '文章分类统计图',
    x: 'center',
    textStyle: {
    color: color
    }
    },
    legend: {
    top: 'bottom',
    data: ${categoryNameJson},
    textStyle: {
    color: color
    }
    },
    tooltip: {
    trigger: 'item'
    },
    series: []
    };
    categoriesOption.series.push(
    categoryParentFlag ?
    {
    nodeClick :false,
    name: '文章篇数',
    type: 'sunburst',
    radius: ['15%', '90%'],
    center: ['50%', '55%'],
    sort: 'desc',
    data: ${categoryArrParentJson},
    itemStyle: {
    borderColor: '#fff',
    borderWidth: 2,
    emphasis: {
    focus: 'ancestor',
    shadowBlur: 10,
    shadowOffsetX: 0,
    shadowColor: 'rgba(255, 255, 255, 0.5)'
    }
    }
    }
    :
    {
    name: '文章篇数',
    type: 'pie',
    radius: [30, 80],
    roseType: 'area',
    label: {
    color: color,
    formatter: '{b} : {c} ({d}%)'
    },
    data: ${categoryArrJson},
    itemStyle: {
    emphasis: {
    shadowBlur: 10,
    shadowOffsetX: 0,
    shadowColor: 'rgba(255, 255, 255, 0.5)'
    }
    }
    }
    )
    categoriesChart.setOption(categoriesOption);
    window.addEventListener('resize', () => {
    categoriesChart.resize();
    });
    categoriesChart.on('click', 'series', (event) => {
    if(event.data.path) window.location.href = '/' + event.data.path;
    });
    </script>`
    }
  4. _config.anzhiyu.yml 中引用 charts.jsInject 插入到 bottom,注意对齐

    1
    - <script src="https://cdn.bootcdn.net/ajax/libs/echarts/4.9.0-rc.1/echarts.min.js"></script> # 统计页面
  5. _config.anzhiyu.ymlmenu 部分进行启用

    1
    2
    3
    分类: /categories/ || anzhiyu-icon-shapes
    标签: /tags/ || anzhiyu-icon-tags
    统计: /charts/ || anzhiyu-icon-charts # 注意对其进行启用

给主题侧边栏添加倒计时

效果展示: 首页个人名片下方展示倒计时

image-20250607192741347

  1. 创建 JS 文件:在博客目录的 source 文件夹下创建 countdown.js 文件(也可以在 source 文件夹下另外新建文件夹)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    const CountdownTimer = (() => {
    const config = {
    targetDate: "2025-01-29",
    targetName: "春节",
    units: {
    day: { text: "今日", unit: "小时" },
    week: { text: "本周", unit: "天" },
    month: { text: "本月", unit: "天" },
    year: { text: "本年", unit: "天" }
    }
    };

    const calculators = {
    day: () => {
    const hours = new Date().getHours();
    return {
    remaining: 24 - hours,
    percentage: (hours / 24) * 100
    };
    },
    week: () => {
    const day = new Date().getDay();
    const passed = day === 0 ? 6 : day - 1;
    return {
    remaining: 6 - passed,
    percentage: ((passed + 1) / 7) * 100
    };
    },
    month: () => {
    const now = new Date();
    const total = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
    const passed = now.getDate() - 1;
    return {
    remaining: total - passed,
    percentage: (passed / total) * 100
    };
    },
    year: () => {
    const now = new Date();
    const start = new Date(now.getFullYear(), 0, 1);
    const total = 365 + (now.getFullYear() % 4 === 0 ? 1 : 0);
    const passed = Math.floor((now - start) / 86400000);
    return {
    remaining: total - passed,
    percentage: (passed / total) * 100
    };
    }
    };

    function updateCountdown() {
    const elements = ['eventName', 'eventDate', 'daysUntil', 'countRight']
    .map(id => document.getElementById(id));

    if (elements.some(el => !el)) return;

    const [eventName, eventDate, daysUntil, countRight] = elements;
    const now = new Date();
    const target = new Date(config.targetDate);

    eventName.textContent = config.targetName;
    eventDate.textContent = config.targetDate;
    daysUntil.textContent = Math.round((target - now.setHours(0,0,0,0)) / 86400000);

    countRight.innerHTML = Object.entries(config.units)
    .map(([key, {text, unit}]) => {
    const {remaining, percentage} = calculators[key]();
    return `
    <div class="cd-count-item">
    <div class="cd-item-name">${text}</div>
    <div class="cd-item-progress">
    <div class="cd-progress-bar" style="width: ${percentage}%; opacity: ${percentage/100}"></div>
    <span class="cd-percentage ${percentage >= 46 ? 'cd-many' : ''}">${percentage.toFixed(2)}%</span>
    <span class="cd-remaining ${percentage >= 60 ? 'cd-many' : ''}">
    <span class="cd-tip">还剩</span>${remaining}<span class="cd-tip">${unit}</span>
    </span>
    </div>
    </div>
    `;
    }).join('');
    }

    function injectStyles() {
    const styles = `
    .card-countdown .item-content {
    display: flex;
    }
    .cd-count-left {
    position: relative;
    display: flex;
    flex-direction: column;
    margin-right: 0.8rem;
    line-height: 1.5;
    align-items: center;
    justify-content: center;
    }
    .cd-count-left .cd-text {
    font-size: 14px;
    }
    .cd-count-left .cd-name {
    font-weight: bold;
    font-size: 18px;
    }
    .cd-count-left .cd-time {
    font-size: 30px;
    font-weight: bold;
    color: var(--anzhiyu-main);
    }
    .cd-count-left .cd-date {
    font-size: 12px;
    opacity: 0.6;
    }
    .cd-count-left::after {
    content: "";
    position: absolute;
    right: -0.8rem;
    width: 2px;
    height: 80%;
    background-color: var(--anzhiyu-main);
    opacity: 0.5;
    }
    .cd-count-right {
    flex: 1;
    margin-left: .8rem;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    }
    .cd-count-item {
    display: flex;
    flex-direction: row;
    align-items: center;
    height: 24px;
    }
    .cd-item-name {
    font-size: 14px;
    margin-right: 0.8rem;
    white-space: nowrap;
    }
    .cd-item-progress {
    position: relative;
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    height: 100%;
    width: 100%;
    border-radius: 8px;
    background-color: var(--anzhiyu-background);
    overflow: hidden;
    }
    .cd-progress-bar {
    height: 100%;
    border-radius: 8px;
    background-color: var(--anzhiyu-main);
    }
    .cd-percentage,
    .cd-remaining {
    position: absolute;
    font-size: 12px;
    margin: 0 6px;
    transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
    }
    .cd-many {
    color: #fff;
    }
    .cd-remaining {
    opacity: 0;
    transform: translateX(10px);
    }
    .card-countdown .item-content:hover .cd-remaining {
    transform: translateX(0);
    opacity: 1;
    }
    .card-countdown .item-content:hover .cd-percentage {
    transform: translateX(-10px);
    opacity: 0;
    }
    `;

    const styleSheet = document.createElement("style");
    styleSheet.textContent = styles;
    document.head.appendChild(styleSheet);
    }

    let timer;
    const start = () => {
    injectStyles();
    updateCountdown();
    timer = setInterval(updateCountdown, 600000);
    };

    ['pjax:complete', 'DOMContentLoaded'].forEach(event => document.addEventListener(event, start));
    document.addEventListener('pjax:send', () => timer && clearInterval(timer));

    return { start, stop: () => timer && clearInterval(timer) };
    })();
  2. 添加组件配置:在 source/_data/widget.yml 中添加以下配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    top:
    - class_name: card-countdown
    id_name:
    name:
    icon:
    html: |
    <div class="cd-count-left">
    <span class="cd-text">距离</span>
    <span class="cd-name" id="eventName"></span>
    <span class="cd-time" id="daysUntil"></span>
    <span class="cd-date" id="eventDate"></span>
    </div>
    <div id="countRight" class="cd-count-right"></div>
  3. 引入 JS 文件:在 _config.anzhiyu.yml 主题配置文件的 inject 配置项的 bottom 中引入 JS 文件:

    1
    2
    3
    inject:
    bottom:
    - <script src="/static/countdown.js"></script>
  4. 配置目标日期

    1
    2
    3
    4
    5
    const config = {
    targetDate: "2026-02-17", // 目标日期
    targetName: "春节", // 名称
    ...
    }

博客添加一个宠物挂件

  1. 顶部挂件(可选): 直接替换 themes\anzhiyu\layout\includes\bbTimeList.pug 文件就行了,代码放下面了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    if site.data.essay
    each i in site.data.essay
    if i.home_essay
    - let onclick_value = theme.pjax.enable ? `pjax.loadUrl("/essay/");` : '';
    - let href_value = theme.pjax.enable ? 'javascript:void(0);' : `/essay/`;
    #bbTimeList.bbTimeList.container
    i.anzhiyufont.anzhiyu-icon-jike.bber-logo.fontbold(onclick=onclick_value, title="即刻短文", href=href_value, aria-hidden="true")
    #bbtalk.swiper-container.swiper-no-swiping.essay_bar_swiper_container(tabindex="-1")
    #bber-talk.swiper-wrapper(onclick=onclick_value)
    each i in site.data.essay
    each item, index in i.essay_list
    if index < 10
    - var contentText = item.image ? item.content + ' [图片]' : (item.video ? item.content + ' [视频]' : item.content)
    a.li-style.swiper-slide(href=href_value)= contentText
    a.bber-gotobb.anzhiyufont.anzhiyu-icon-circle-arrow-right(onclick=onclick_value, href=href_value, title="查看全文")
    img.con-animals.entered.loaded(id="new-con-animals" src="")
    script(src=url_for(theme.home_top.swiper.swiper_js))

    style.
    #bbTimeList {
    position: relative;
    }

    .con-animals {
    display: block;
    position: absolute;
    max-width: 260px;
    top: -85px;
    z-index: 2;
    }

    @media screen and (max-width: 1200px) {
    .con-animals {
    display: none;
    }
    }

    script.
    // 将lastImageUrl移到全局作用域
    window.lastImageUrl = window.lastImageUrl || '';

    function setRandomImage() {
    const img = document.getElementById('new-con-animals');
    const imageUrls = [
    "https://i1.wp.com/ruom.wuaze.com/i/2024/10/18/901216.webp",
    "https://i1.wp.com/ruom.wuaze.com/i/2024/10/18/074167.webp",
    "https://i1.wp.com/ruom.wuaze.com/i/2024/10/19/759434.webp",
    "https://i1.wp.com/ruom.wuaze.com/i/2024/10/19/526748.webp",
    "https://i1.wp.com/ruom.wuaze.com/i/2024/10/18/429029.webp"
    ];

    let randomImage;
    do {
    randomImage = imageUrls[Math.floor(Math.random() * imageUrls.length)];
    } while (randomImage === window.lastImageUrl);

    window.lastImageUrl = randomImage;

    if (img) {
    img.src = randomImage;
    }
    }

    function initializeDragImage() {
    const img = document.getElementById('new-con-animals');
    const container = document.getElementById('bbTimeList');

    if (!img || !container) return;

    if (!window.lastImageUrl) {
    setRandomImage();
    } else {
    img.src = window.lastImageUrl;
    }

    let isDragging = false, wasDragged = false, startX, startLeft;
    const containerWidth = container.clientWidth;
    const imgWidth = img.clientWidth;
    const maxLeft = containerWidth - imgWidth;
    const edgeThreshold = 20;
    let lastLeft = parseInt(localStorage.getItem('imgPositionLeft')) || 0;
    lastLeft = Math.min(maxLeft, Math.max(0, lastLeft));
    img.style.left = `${lastLeft}px`;

    const savePosition = (left) => localStorage.setItem('imgPositionLeft', left);

    img.addEventListener('click', () => {
    if (!wasDragged) {
    let currentLeft = lastLeft;
    let newLeft;

    if (currentLeft <= edgeThreshold) {
    newLeft = Math.min(currentLeft + 200, maxLeft);
    } else if (currentLeft >= maxLeft - edgeThreshold) {
    newLeft = Math.max(currentLeft - 200, 0);
    } else {
    newLeft = currentLeft + (Math.random() < 0.5 ? -200 : 200);
    newLeft = Math.max(0, Math.min(newLeft, maxLeft));
    }

    if (newLeft !== lastLeft) {
    lastLeft = newLeft;
    img.style.left = `${newLeft}px`;
    savePosition(newLeft);
    }
    }
    });

    img.addEventListener('mousedown', (e) => {
    isDragging = true;
    wasDragged = false;
    startX = e.clientX;
    startLeft = lastLeft;
    img.style.transition = 'none';

    const onMouseMove = (e) => {
    if (!isDragging) return;
    wasDragged = true;
    const offsetX = e.clientX - startX;
    lastLeft = Math.max(0, Math.min(startLeft + offsetX, maxLeft));
    requestAnimationFrame(() => {
    img.style.left = `${lastLeft}px`;
    });
    };

    const onMouseUp = () => {
    isDragging = false;
    img.style.transition = 'left 0.5s ease-in-out';
    savePosition(lastLeft);
    document.removeEventListener('mousemove', onMouseMove);
    document.removeEventListener('mouseup', onMouseUp);
    };

    document.addEventListener('mousemove', onMouseMove);
    document.addEventListener('mouseup', onMouseUp);
    });
    }

    document.addEventListener('DOMContentLoaded', initializeDragImage);
    document.addEventListener('pjax:success', initializeDragImage);

    如果你发现夜间模式会被遮挡一部分可以添加下这段 css 试试,暂且不知道会不会影响其它部分

    1
    2
    3
    4
    5
    6
    7
    8
    /* 小动物夜间显示优化 */
    [data-theme='dark'] #page-header.nav-fixed #nav {
    background: var(--anzhiyu-black)!important;
    }

    [data-theme='dark'] #page-header #nav {
    background: 0 !important;
    }
  2. 页脚挂件: 需要开启主题配置文件中的 footer_bar 选项,设置为 true。新建一个 footer-animal.js 文件,将以下代码粘贴进去。然后在 _config.anzhiyu.ymlinject 选项的 bottom 中添加 <script src="/static/footer-animal.js" defer></script>。 根据你文件位置,自行调整。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    function initFooterAnimal() {
    const footerBar = document.querySelector('#footer-bar');
    if (!footerBar) return console.error('找不到指定元素');

    const footerAnimal = document.createElement('div');
    footerAnimal.id = 'footer-animal';
    footerAnimal.innerHTML = `
    <img class="animal entered loaded"
    src="https://i1.wp.com/ruom.wuaze.com/i/2024/10/19/473503.webp"
    alt="动物" />
    `;

    footerBar.insertAdjacentElement('beforebegin', footerAnimal);

    const style = document.createElement('style');
    style.textContent = `
    #footer-animal {
    position: relative;
    }
    #footer-animal::before {
    content: '';
    position: absolute;
    bottom: 0;
    width: 100%;
    height: 36px;
    background: url(https://i1.wp.com/ruom.wuaze.com/i/2024/10/19/351933.webp) repeat center / auto 100%;
    box-shadow: 0 4px 7px rgba(0,0,0,.15);
    }
    .animal {
    position: relative;
    max-width: min(974px, 100vw);
    margin: 0 auto;
    display: block;
    }
    #footer-bar {
    margin-top: 0 !important;
    }
    @media screen and (max-width: 1023px) {
    #footer-animal::before {
    height: 4vw;
    }
    }
    [data-theme=dark] #footer-animal {
    filter: brightness(.8);
    }
    `;
    document.head.appendChild(style);
    }

    document.addEventListener('DOMContentLoaded', initFooterAnimal);
    document.addEventListener('pjax:success', initFooterAnimal);

添加一个昼夜切换按钮(GitHub 源

  1. 在路径 themes/anzhiyu/layout/includes/anzhiyu/ 创建 Day_night_toggle_button.pug 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55

    .container
    .components
    .main-button
    .moon
    .moon
    .moon
    .daytime-backgrond
    .daytime-backgrond
    .daytime-backgrond
    .cloud
    .cloud-son
    .cloud-son
    .cloud-son
    .cloud-son
    .cloud-son
    .cloud-son
    .cloud-light
    .cloud-son
    .cloud-son
    .cloud-son
    .cloud-son
    .cloud-son
    .cloud-son
    .stars
    .star.big
    .star-son
    .star-son
    .star-son
    .star-son
    .star.big
    .star-son
    .star-son
    .star-son
    .star-son
    .star.medium
    .star-son
    .star-son
    .star-son
    .star-son
    .star.medium
    .star-son
    .star-son
    .star-son
    .star-son
    .star.small
    .star-son
    .star-son
    .star-son
    .star-son
    .star.small
    .star-son
    .star-son
    .star-son
    .star-son
  2. 中控台引用:themes/anzhiyu/layout/includes/anzhiyu/console.pug

    1
    2
    3
    4
    5
    6
    7
    8
    9

    if theme.shortcutKey.enable
    .console-btn-item#consoleKeyboard(onclick='anzhiyu.keyboardToggle()', title='快捷键开关')
    a.keyboard-switch
    i.naokuofont.naokuo-icon-keyboard
    + if theme.Day_night_toggle_button.console
    + #console-naoDark
    + !=partial('includes/anzhiyu/Day_night_toggle_button', {}, {cache: true})
    .console-mask(onclick='anzhiyu.hideConsole()', href='javascript:void(0);')
  3. 导航栏引用:themes/anzhiyu/layout/includes/header/nav.pug

    1
    2
    3
    4
    5
    6
        #menus
    !=partial('includes/header/menu_item', {}, {cache: true})
    #nav-right
    + if theme.Day_night_toggle_button.nav
    + #nav-naoDark
    + !=partial('includes/anzhiyu/Day_night_toggle_button', {}, {cache: true})
  4. 在路径 themes/anzhiyu/source/css/_layout/ 创建 Day_night_toggle_button.styl

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369

    if hexo-config('Day_night_toggle_button.console') || hexo-config('Day_night_toggle_button.nav')
    .container
    position relative
    width 180em
    height 70em
    display inline-block
    vertical-align bottom
    transform translate3d(0, 0, 0)
    .components
    position fixed
    width 180em
    height 70em
    background-color rgba(70, 133, 192, 1)
    border-radius 100em
    box-shadow inset 0 0 5em 3em rgba(0, 0, 0, 0.5)
    overflow hidden
    transition 0.7s
    transition-timing-function cubic-bezier(0, 0.5, 1, 1)
    cursor pointer
    .main-button
    margin 7.5em 0 0 7.5em
    width 55em
    height 55em
    background-color rgba(255, 195, 35, 1)
    border-radius 50%
    box-shadow 3em 3em 5em rgba(0, 0, 0, 0.5), inset -3em -5em 3em -3em rgba(0, 0, 0, 0.5), inset 4em 5em 2em -2em rgba(255, 230, 80, 1)
    transition 1.0s
    transition-timing-function cubic-bezier(0.56, 1.35, 0.52, 1.00)
    .moon
    position absolute
    background-color rgba(150, 160, 180, 1)
    box-shadow inset 0em 0em 1em 1em rgba(0, 0, 0, 0.3)
    border-radius 50%
    transition 0.5s
    opacity 0
    &:nth-child(1)
    top 7.5em
    left 25em
    width 12.5em
    height 12.5em
    &:nth-child(2)
    top 20em
    left 7.5em
    width 20em
    height 20em
    &:nth-child(3)
    top 32.5em
    left 32.5em
    width 12.5em
    height 12.5em
    .daytime-backgrond
    position absolute
    border-radius 50%
    transition 1.0s
    transition-timing-function cubic-bezier(0.56, 1.35, 0.52, 1.00)
    &:nth-child(2)
    top -20em
    left -20em
    width 110em
    height 110em
    background-color rgba(255, 255, 255, 0.2)
    z-index -2
    &:nth-child(3)
    top -32.5em
    left -17.5em
    width 135em
    height 135em
    background-color rgba(255, 255, 255, 0.1)
    z-index -3
    &:nth-child(4)
    top -45em
    left -15em
    width 160em
    height 160em
    background-color rgba(255, 255, 255, 0.05)
    z-index -4
    .cloud,
    .cloud-light
    transform translateY(10em)
    transition 1.0s
    transition-timing-function cubic-bezier(0.56, 1.35, 0.52, 1.00)
    .cloud-son
    position absolute
    background-color #fff
    border-radius 50%
    z-index -1
    transition transform 6s, right 1s, bottom 1s
    &:nth-child(6n
    & + 1)
    right -20em
    bottom 10em
    width 50em
    height 50em
    & + 2)
    right -10em
    bottom -25em
    width 60em
    height 60em
    & + 3)
    right 20em
    bottom -40em
    width 60em
    height 60em
    & + 4)
    right 50em
    bottom -35em
    width 60em
    height 60em
    & + 5)
    right 75em
    bottom -60em
    width 75em
    height 75em
    & + 6)
    right 110em
    bottom -50em
    width 60em
    height 60em
    .cloud
    z-index -2
    .cloud-light
    position absolute
    right 0em
    bottom 25em
    opacity 0.5
    z-index -3
    .stars
    transform translateY(-125em)
    z-index -2
    transition 1.0s
    transition-timing-function cubic-bezier(0.56, 1.35, 0.52, 1.00)
    .big
    --size 7.5em
    .medium
    --size 5em
    .small
    --size 3em
    .star
    position absolute
    width calc(2*var(--size))
    height calc(2*var(--size))
    &:nth-child(1)
    top 11em
    left 39em
    &:nth-child(2)
    top 39em
    left 91em
    &:nth-child(3)
    top 26em
    left 19em
    &:nth-child(4)
    top 37em
    left 66em
    &:nth-child(5)
    top 21em
    left 75em
    &:nth-child(6)
    top 51em
    left 38em
    .star-son
    float left
    &:nth-child(1)
    --pos left 0
    &:nth-child(2)
    --pos right 0
    &:nth-child(3)
    --pos 0 bottom
    &:nth-child(4)
    --pos right bottom
    .star-son
    width var(--size)
    height var(--size)
    background-image radial-gradient(circle var(--size) at var(--pos), transparent calc(95% - 1px), #fff 95%)
    .components
    .main-button
    &:hover
    transform: translateX(10em);
    & ~ .daytime-backgrond
    &:nth-child(2)
    transform: translateX(10em);
    &:nth-child(3)
    transform: translateX(7em);
    &:nth-child(4)
    transform: translateX(4em);
    & ~ .cloud .cloud-son
    &:nth-child(1)
    right -24em
    bottom 10em
    &:nth-child(2)
    right -12em
    bottom -27em
    &:nth-child(3)
    right 17em
    bottom -43em
    &:nth-child(4)
    right 46em
    bottom -39em
    &:nth-child(5)
    right 70em
    bottom -65em
    &:nth-child(6)
    right 109em
    bottom -54em
    & ~ .cloud-light .cloud-son
    &:nth-child(1)
    right -23em
    bottom 10em
    &:nth-child(2)
    right -11em
    bottom -26em
    &:nth-child(3)
    right 18em
    bottom -42em
    &:nth-child(4)
    right 47em
    bottom -38em
    &:nth-child(5)
    right 74em
    bottom -64em
    &:nth-child(6)
    right 110em
    bottom -55em

    @keyframes naoStar {
    0%,
    20% {
    transform: scale(0);
    }
    20%,
    100% {
    transform: scale(1);
    }
    }
    // 云朵动画
    @keyframes naoCloud {
    0% {
    transform: translate(2em, -2em);
    }
    50% {
    transform: translate(-2em, 2em);
    }
    100% {
    transform: translate(2em, -2em);
    }
    }
    [data-theme="light"] #console-naoDark,
    [data-theme="light"] #nav-naoDark
    .container
    .cloud-son
    transform scale(1)
    transition-timing-function cubic-bezier(0.56, 1.35, 0.52, 1.00)
    transition 1s
    animation-iteration-count infinite
    animation-direction alternate
    animation-timing-function ease-in-out
    &:nth-child(6n + 1)
    animation-name naoCloud
    animation-duration 4.5s
    &:nth-child(6n + 2)
    animation-name naoCloud
    animation-duration 5.1s
    &:nth-child(6n + 3)
    animation-name naoCloud
    animation-duration 5.9s
    &:nth-child(6n + 4)
    animation-name naoCloud
    animation-duration 6.3s
    &:nth-child(6n + 5)
    animation-name naoCloud
    animation-duration 4.7s
    &:nth-child(6n + 6)
    animation-name naoCloud
    animation-duration 5s

    [data-theme="dark"] #console-naoDark,
    [data-theme="dark"] #nav-naoDark
    .container
    .components
    background-color: rgba(25,30,50,1)
    .main-button
    transform: translateX(110em)
    background-color: rgba(195, 200,210,1)
    box-shadow: 3em 3em 5em rgba(0, 0, 0, 0.5), inset -3em -5em 3em -3em rgba(0, 0, 0, 0.5), inset 4em 5em 2em -2em rgba(255, 255, 210,1)
    .moon
    &:nth-child(1)
    opacity: 1
    &:nth-child(2)
    opacity: 1
    &:nth-child(3)
    opacity: 1
    .daytime-backgrond
    &:nth-child(2)
    transform: translateX(110em)
    &:nth-child(3)
    transform: translateX(80em)
    &:nth-child(4)
    transform: translateX(50em)
    .cloud,
    .cloud-light
    transform translateY(80em)
    .stars
    transform translateY(-62.5em)
    opacity: 1
    .star
    transform scale(1)
    transition-timing-function cubic-bezier(0.56, 1.35, 0.52, 1.00)
    transition 1s
    animation-iteration-count infinite
    animation-direction alternate
    animation-timing-function linear
    &:nth-child(1)
    animation-name naoStar
    animation-duration 3.5s
    &:nth-child(2)
    animation-name naoStar
    animation-duration 4.1s
    &:nth-child(3)
    animation-name naoStar
    animation-duration 4.9s
    &:nth-child(4)
    animation-name naoStar
    animation-duration 5.3s
    &:nth-child(5)
    animation-name naoStar
    animation-duration 3s
    &:nth-child(6)
    animation-name naoStar
    animation-duration 2.2s
    .components
    .main-button
    &:hover
    transform: translateX(100em)
    & ~ .daytime-backgrond
    &:nth-child(2)
    transform: translateX(100em)
    &:nth-child(3)
    transform: translateX(73em)
    &:nth-child(4)
    transform: translateX(46em)
    & ~ .stars .star
    &:nth-child(1)
    top: 10em
    left: 36em
    &:nth-child(2)
    top: 40em
    left: 87em
    &:nth-child(3)
    top: 26em
    left: 16em
    &:nth-child(4)
    top: 38em
    left: 63em
    &:nth-child(5)
    top: 20.5em
    left: 72em
    &:nth-child(6)
    top: 51.5em
    left: 35em

    #nav-right
    if hexo-config('Day_night_toggle_button.console')
    #console-naoDark
    .container
    font-size: 0.85px
    if hexo-config('Day_night_toggle_button.nav')
    #nav-naoDark
    .container
    font-size: 0.5px
  5. 添加按钮交互:在路径 themes/anzhiyu/source/js/main.js 添加如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      window.refreshFn = function () {
    initAdjust();
    themeColorMeta = document.querySelector('meta[name="theme-color"]');
    pageHeaderEl = document.getElementById("page-header");
    navMusicEl = document.getElementById("nav-music");
    consoleEl = document.getElementById("console");

    addDarkModeEventListener("console", ".darkmode_switchbutton");
    + addDarkModeEventListener("nav-naoDark", ".components");
    + addDarkModeEventListener("console-naoDark", ".components");
  6. 主题配置文件 _config.anzhiyu.yml 添加:

    1
    2
    3
    4
    # 昼夜切换按钮
    Day_night_toggle_button:
    console: true # 中控台
    nav: true # 导航栏

为主页文章卡片添加擦亮动画效果

  1. 新增 css 内容:新建文件 source/css/home.css 或在已引入的 css 中新增以下内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #recent-posts > .recent-post-item:not(a)::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 200%;
    background: linear-gradient(to right, transparent, white, transparent);
    transform: translateX(-200%);
    transition: transform 0.5s linear;
    z-index: 1;
    }
    #recent-posts > .recent-post-item:not(a):hover::before {
    transform: translateX(100%) skewX(-60deg);
    }
  2. 引入内容:在 _config.anzhiyu.yml 主题配置文件下 inject 配置项中 head 处引入 home.css 文件

    1
    2
    3
    inject:
    head:
    - <link rel="stylesheet" href="/css/home.css"> # 首页文章卡片擦亮效果

文章加密插件(GitHub 源

  1. 在根目录执行以下命令:

    1
    npm install --save hexo-blog-encrypt
  2. Front matter 配置方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ---
    title: Hello World
    tags:
    - 作为日记加密
    date: 2016-03-30 21:12:21
    password: mikemessi
    abstract: 有东西被加密了, 请输入密码查看。
    message: 您好, 这里需要密码。
    theme: xray
    wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试。
    wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容。
    ---

    如果你在 front-matter 里添加了 password 字段,这篇文章就会 单独加密,优先级高于标签加密。

    字段名 说明 示例
    password 设置该文章的专属访问密码 mikemessi,用户输入正确才能解密查看内容
    abstract 单独覆盖全局摘要提示(可选) 比如为某一篇文章自定义一段介绍
    message 单独覆盖全局提示信息 可针对文章内容使用不同文案
    wrong_pass_message 密码错误时的反馈文案(可选) 便于 UX 个性化
    wrong_hash_message 解密内容校验失败的提示(可选) 一般不必修改

    注意:

    • 如果同时设置了全局标签加密和文章局部密码加密,文章 front-matter 中的 password 优先。
    • abstractmessage 都是展示在前端的提示文字,不影响实际加密逻辑。
  3. 配置文件 [BlogRoot]\_config.yml 中针对 tags 的加密

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # Security
    encrypt: # hexo-blog-encrypt
    abstract: 有东西被加密了, 请输入密码查看。
    message: 您好, 这里需要密码。
    tags:
    - {name: tagName, password: 密码A}
    - {name: tagName, password: 密码B}
    theme: xray
    wrong_pass_message: 抱歉, 这个密码看着不太对, 请再试试。
    wrong_hash_message: 抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容。
字段名 说明 示例/备注
abstract 文章加密后显示的摘要/预览文字 提示用户文章是加密的
message 加密文章的提示语 通常告诉用户需要输入密码
tags 指定标签加密:只要文章含有指定 name 的标签就会被加密,密码为对应的 password 适用于批量加密
wrong_pass_message 密码错误时显示的消息 可自定义提示
wrong_hash_message 解密成功但内容校验失败时显示的消息(一般发生在内容被篡改后) 多用于调试场景

敬请期待……