440 lines
12 KiB
Markdown
440 lines
12 KiB
Markdown
---
|
||
title: 在侧边栏添加日历和倒计时
|
||
categories: 建站手札
|
||
cover: https://pic.biss.click/i/2025/08/30/m7amqo.png
|
||
tags: 网站
|
||
series: webcustom
|
||
summary: 实现了一个侧边栏日历与倒计时卡片,包含以下核心功能:日历展示:显示当前日期(公历与农历)、星期、生肖、干支年份。动态生成当月日历网格,高亮当天日期。倒计时模块:距离除夕倒计时:实时计算并显示剩余天数(示例中为2026年2月16日)。进度条:分别展示本年、本月、本周的已过去进度(百分比)及剩余天数。农历支持:调用 chinese-lunar.js 库将公历转换为农历,格式化显示为“干支年+生肖年+农历月+农历日”。
|
||
abbrlink: 5ed2f1e6
|
||
date: 2025-08-11 10:32:44
|
||
---
|
||
突然看到某个网站侧边栏有日历和倒计时,就研究了一下,抄下来了(🤭)
|
||
效果图:
|
||
|
||
<center><img src="https://pic.biss.click/i/2025/08/30/kq76e0.png" alt="效果图"></center>
|
||
|
||
# 添加js
|
||
|
||
```js
|
||
document.addEventListener("DOMContentLoaded", () => {
|
||
initializeCard();
|
||
});
|
||
|
||
document.addEventListener("pjax:complete", () => {
|
||
initializeCard();
|
||
});
|
||
|
||
function initializeCard() {
|
||
cardTimes();
|
||
cardRefreshTimes();
|
||
}
|
||
|
||
let year, month, week, date, dates, weekStr, monthStr, asideTime, asideDay, asideDayNum, animalYear, ganzhiYear, lunarMon, lunarDay;
|
||
const now = new Date();
|
||
|
||
function cardRefreshTimes() {
|
||
const e = document.getElementById("card-widget-schedule");
|
||
if (e) {
|
||
asideDay = (now - asideTime) / 1e3 / 60 / 60 / 24;
|
||
e.querySelector("#pBar_year").value = asideDay;
|
||
e.querySelector("#p_span_year").innerHTML = (asideDay / 365 * 100).toFixed(1) + "%";
|
||
e.querySelector(".schedule-r0 .schedule-d1 .aside-span2").innerHTML = `还剩<a> ${(365 - asideDay).toFixed(0)} </a>天`;
|
||
e.querySelector("#pBar_month").value = date;
|
||
e.querySelector("#pBar_month").max = dates;
|
||
e.querySelector("#p_span_month").innerHTML = (date / dates * 100).toFixed(1) + "%";
|
||
e.querySelector(".schedule-r1 .schedule-d1 .aside-span2").innerHTML = `还剩<a> ${(dates - date)} </a>天`;
|
||
e.querySelector("#pBar_week").value = week === 0 ? 7 : week;
|
||
e.querySelector("#p_span_week").innerHTML = ((week === 0 ? 7 : week) / 7 * 100).toFixed(1) + "%";
|
||
e.querySelector(".schedule-r2 .schedule-d1 .aside-span2").innerHTML = `还剩<a> ${(7 - (week === 0 ? 7 : week))} </a>天`;
|
||
}
|
||
}
|
||
|
||
function cardTimes() {
|
||
year = now.getFullYear();
|
||
month = now.getMonth();
|
||
week = now.getDay();
|
||
date = now.getDate();
|
||
|
||
const e = document.getElementById("card-widget-calendar");
|
||
if (e) {
|
||
const isLeapYear = year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
|
||
weekStr = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"][week];
|
||
const monthData = [
|
||
{ month: "1月", days: 31 },
|
||
{ month: "2月", days: isLeapYear ? 29 : 28 },
|
||
{ month: "3月", days: 31 },
|
||
{ month: "4月", days: 30 },
|
||
{ month: "5月", days: 31 },
|
||
{ month: "6月", days: 30 },
|
||
{ month: "7月", days: 31 },
|
||
{ month: "8月", days: 31 },
|
||
{ month: "9月", days: 30 },
|
||
{ month: "10月", days: 31 },
|
||
{ month: "11月", days: 30 },
|
||
{ month: "12月", days: 31 }
|
||
];
|
||
monthStr = monthData[month].month;
|
||
dates = monthData[month].days;
|
||
|
||
const t = (week + 8 - date % 7) % 7;
|
||
let n = "", d = false, s = 7 - t;
|
||
const o = (dates - s) % 7 === 0 ? Math.floor((dates - s) / 7) + 1 : Math.floor((dates - s) / 7) + 2;
|
||
const c = e.querySelector("#calendar-main");
|
||
const l = e.querySelector("#calendar-date");
|
||
|
||
l.style.fontSize = ["64px", "48px", "36px"][Math.min(o - 3, 2)];
|
||
|
||
for (let i = 0; i < o; i++) {
|
||
if (!c.querySelector(`.calendar-r${i}`)) {
|
||
c.innerHTML += `<div class='calendar-r${i}'></div>`;
|
||
}
|
||
for (let j = 0; j < 7; j++) {
|
||
if (i === 0 && j === t) {
|
||
n = 1;
|
||
d = true;
|
||
}
|
||
const r = n === date ? " class='now'" : "";
|
||
if (!c.querySelector(`.calendar-r${i} .calendar-d${j} a`)) {
|
||
c.querySelector(`.calendar-r${i}`).innerHTML += `<div class='calendar-d${j}'><a${r}>${n}</a></div>`;
|
||
}
|
||
if (n >= dates) {
|
||
n = "";
|
||
d = false;
|
||
}
|
||
if (d) {
|
||
n += 1;
|
||
}
|
||
}
|
||
}
|
||
|
||
const lunarDate = chineseLunar.solarToLunar(new Date(year, month, date));
|
||
animalYear = chineseLunar.format(lunarDate, "A");
|
||
ganzhiYear = chineseLunar.format(lunarDate, "T").slice(0, -1);
|
||
lunarMon = chineseLunar.format(lunarDate, "M");
|
||
lunarDay = chineseLunar.format(lunarDate, "d");
|
||
|
||
const newYearDate = new Date("2026/02/16 00:00:00");
|
||
const daysUntilNewYear = Math.floor((newYearDate - now) / 1e3 / 60 / 60 / 24);
|
||
asideTime = new Date(`${new Date().getFullYear()}/01/01 00:00:00`);
|
||
asideDay = (now - asideTime) / 1e3 / 60 / 60 / 24;
|
||
asideDayNum = Math.floor(asideDay);
|
||
const weekNum = week - asideDayNum % 7 >= 0 ? Math.ceil(asideDayNum / 7) : Math.ceil(asideDayNum / 7) + 1;
|
||
|
||
e.querySelector("#calendar-week").innerHTML = `第${weekNum}周 ${weekStr}`;
|
||
e.querySelector("#calendar-date").innerHTML = date.toString().padStart(2, "0");
|
||
e.querySelector("#calendar-solar").innerHTML = `${year}年${monthStr} 第${asideDay.toFixed(0)}天`;
|
||
e.querySelector("#calendar-lunar").innerHTML = `${ganzhiYear}${animalYear}年 ${lunarMon}${lunarDay}`;
|
||
document.getElementById("schedule-days").innerHTML = daysUntilNewYear;
|
||
}
|
||
}
|
||
```
|
||
|
||
# 添加css
|
||
|
||
在主题文件 `source/css/`新建 `calendar.css`。
|
||
|
||
```css
|
||
/* 浅色主题变量覆盖 -------------------------------- */
|
||
:root {
|
||
--anzhiyu-main: #a0d2eb; /* 主强调色:柔和天空蓝 */
|
||
--anzhiyu-main-op: rgba(160, 210, 235, 0.6);
|
||
--anzhiyu-main-op-deep: rgba(160, 210, 235, 0.4);
|
||
--anzhiyu-main-op-light: rgba(160, 210, 235, 0.2);
|
||
|
||
--efu-card-bg: #fdfdfd; /* 卡片背景:几乎白 */
|
||
--efu-fontcolor: #444; /* 主文本:深灰 */
|
||
--efu-secondtext: #999; /* 次级文本:浅灰 */
|
||
}
|
||
.card-widget {
|
||
padding: 10px!important;
|
||
max-height: calc(100vh - 100px);
|
||
}
|
||
.card-times a, .card-times div {
|
||
color: var(--efu-fontcolor);
|
||
}
|
||
|
||
#card-widget-calendar .item-content {
|
||
display: flex;
|
||
}
|
||
|
||
#calendar-area-left {
|
||
width: 45%;
|
||
}
|
||
|
||
#calendar-area-right {
|
||
width: 55%;
|
||
}
|
||
|
||
#calendar-area-left, #calendar-area-right {
|
||
height: 100%;
|
||
padding: 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
#calendar-main {
|
||
width: 100%;
|
||
}
|
||
|
||
#calendar-week {
|
||
height: 1.2rem;
|
||
font-size: 14px;
|
||
letter-spacing: 1px;
|
||
font-weight: 700;
|
||
align-items: center;
|
||
display: flex;
|
||
}
|
||
|
||
#calendar-date {
|
||
height: 3rem;
|
||
line-height: 1.3;
|
||
font-size: 64px;
|
||
letter-spacing: 3px;
|
||
color: var(--anzhiyu-main);
|
||
font-weight: 700;
|
||
align-items: center;
|
||
display: flex;
|
||
position: relative;
|
||
top: calc(50% - 2.1rem);
|
||
}
|
||
|
||
#calendar-lunar, #calendar-solar {
|
||
height: 1rem;
|
||
font-size: 12px;
|
||
align-items: center;
|
||
display: flex;
|
||
position: absolute;
|
||
}
|
||
|
||
#calendar-solar {
|
||
bottom: 2.1rem;
|
||
}
|
||
|
||
#calendar-lunar {
|
||
bottom: 1rem;
|
||
color: var(--efu-secondtext);
|
||
}
|
||
|
||
#calendar-main a {
|
||
height: 1rem;
|
||
width: 1rem;
|
||
border-radius: 50%;
|
||
font-size: 12px;
|
||
line-height: 12px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
#calendar-main a.now {
|
||
background: var(--anzhiyu-main);
|
||
color: var(--efu-card-bg);
|
||
}
|
||
|
||
#calendar-main .calendar-rh a {
|
||
color: var(--efu-secondtext);
|
||
}
|
||
|
||
.calendar-r0, .calendar-r1, .calendar-r2, .calendar-r3, .calendar-r4, .calendar-r5, .calendar-rh {
|
||
height: 1.2rem;
|
||
display: flex;
|
||
}
|
||
|
||
.calendar-d0, .calendar-d1, .calendar-d2, .calendar-d3, .calendar-d4, .calendar-d5, .calendar-d6 {
|
||
width: calc(100% / 7);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
#card-widget-schedule .item-content {
|
||
display: flex;
|
||
}
|
||
|
||
#schedule-area-left, #schedule-area-right {
|
||
height: 100px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
#schedule-area-left {
|
||
width: 30%;
|
||
}
|
||
|
||
#schedule-area-right {
|
||
width: 70%;
|
||
padding: 0 5px;
|
||
}
|
||
|
||
.schedule-r0, .schedule-r1, .schedule-r2 {
|
||
height: 2rem;
|
||
width: 100%;
|
||
align-items: center;
|
||
display: flex;
|
||
}
|
||
|
||
.schedule-d0 {
|
||
width: 30px;
|
||
margin-right: 5px;
|
||
text-align: center;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.schedule-d1 {
|
||
width: calc(100% - 35px);
|
||
height: 1.5rem;
|
||
align-items: center;
|
||
display: flex;
|
||
}
|
||
|
||
progress::-webkit-progress-bar {
|
||
background: linear-gradient(to right, var(--anzhiyu-main-op-deep), var(--anzhiyu-main-op), var(--anzhiyu-main-op-light));
|
||
border-radius: 5px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
progress::-webkit-progress-value {
|
||
background: var(--anzhiyu-main);
|
||
border-radius: 5px;
|
||
}
|
||
|
||
.aside-span1, .aside-span2 {
|
||
height: 1rem;
|
||
font-size: 12px;
|
||
z-index: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
position: absolute;
|
||
}
|
||
|
||
.aside-span1 {
|
||
margin-left: 5px;
|
||
}
|
||
|
||
.aside-span2 {
|
||
right: 20px;
|
||
color: var(--efu-secondtext);
|
||
}
|
||
|
||
.aside-span2 a {
|
||
margin: 0 3px;
|
||
}
|
||
|
||
#pBar_month, #pBar_week, #pBar_year {
|
||
width: 100%;
|
||
border-radius: 5px;
|
||
height: 100%;
|
||
}
|
||
|
||
#schedule-date, #schedule-days, #schedule-title {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
#schedule-title {
|
||
height: 25px;
|
||
line-height: 1;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
#schedule-days {
|
||
height: 40px;
|
||
line-height: 1;
|
||
font-size: 30px;
|
||
font-weight: 900;
|
||
color: var(--anzhiyu-main);
|
||
}
|
||
|
||
#schedule-date {
|
||
height: 20px;
|
||
line-height: 1;
|
||
font-size: 12px;
|
||
color: var(--efu-secondtext);
|
||
}
|
||
```
|
||
|
||
# 引入
|
||
|
||
新增+号后面内容
|
||
|
||
```yml
|
||
inject:
|
||
head:
|
||
# 自定义css
|
||
+ - <link rel="stylesheet" href="/css/calendar.css">
|
||
|
||
bottom:
|
||
# 自定义js
|
||
+ - <script src="/js/calendar.js"></script>
|
||
+ - <script src="https://unpkg.com/chinese-lunar@0.1.4/lib/chinese-lunar.js"></script>
|
||
```
|
||
|
||
在 `blogroot/source/_data` 文件夹下创建 `widget.yml` 文件,并添加以下内容:
|
||
|
||
```yml
|
||
bottom:
|
||
- class_name: calendar
|
||
id_name: card-widget-calendar
|
||
name:
|
||
icon:
|
||
order: -1
|
||
html: |
|
||
<div id="calendar-area-left">
|
||
<div id="calendar-week"></div>
|
||
<div id="calendar-date" style="font-size: 48px;"></div>
|
||
<div id="calendar-solar"></div>
|
||
<div id="calendar-lunar"></div>
|
||
</div>
|
||
<div id="calendar-area-right">
|
||
<div id="calendar-main">
|
||
</div>
|
||
</div>
|
||
|
||
- class_name: schedule
|
||
id_name: card-widget-schedule
|
||
name:
|
||
icon:
|
||
order: -1
|
||
html: |
|
||
<div id="schedule-area-left">
|
||
<div id="schedule-title">距离除夕</div>
|
||
<div id="schedule-days"></div>
|
||
<div id="schedule-date">2025-01-28</div>
|
||
</div>
|
||
<div id="schedule-area-right">
|
||
<div class="schedule-r0">
|
||
<div class="schedule-d0">本年</div>
|
||
<div class="schedule-d1">
|
||
<span id="p_span_year" class="aside-span1"></span>
|
||
<span class="aside-span2">还剩<a></a>天</span>
|
||
<progress max="365" id="pBar_year"></progress>
|
||
</div>
|
||
</div>
|
||
<div class="schedule-r1">
|
||
<div class="schedule-d0">本月</div>
|
||
<div class="schedule-d1">
|
||
<span id="p_span_month" class="aside-span1"></span>
|
||
<span class="aside-span2">还剩<a></a>天</span>
|
||
<progress max="30" id="pBar_month"></progress>
|
||
</div>
|
||
</div>
|
||
<div class="schedule-r2">
|
||
<div class="schedule-d0">本周</div>
|
||
<div class="schedule-d1">
|
||
<span id="p_span_week" class="aside-span1"></span>
|
||
<span class="aside-span2">还剩<a></a>天</span>
|
||
<progress max="7" id="pBar_week"></progress>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
# 参考
|
||
|
||
{% link 乐活星球,lohas,https://blog.lohas.fun/hc/jc005/ %}
|