第七篇 Hexo 魔改实录 | 配置倒计时挂件

前言

这里主要是参考梦爱吃鱼大佬的博客来完成的,具体效果如下:

具体流程

1 创建 JS 文件

  1. 在博客目录的 source 文件夹下创建 countdown.js 文件(也可以在 source 文件夹下另外新建文件夹)。
  2. 这里为了方便管理,我在 source 文件夹下新建了一个 static 文件夹,然后将 countdown.js 文件放在 static 文件夹下。
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: "2026-01-01",
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 中添加以下配置,如果没有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>

注意:static 是我在 source 文件夹下创建的文件夹,具体根据实际情况修改!

4 配置目标日期

  • 这里修改我们之前创建的 countdown.js 文件,将目标日期修改为自己的目标日期。
1
2
3
4
5
const config = {
targetDate: "2026-01-01", // 目标日期
targetName: "元旦", // 名称
...
}