---
title: 在侧边栏添加日历和倒计时
categories: 建站手札
cover: https://pic.biss.click/image/232af9c6-97ce-42f8-85a8-9338963066e6.webp
tags: 网站
series: webcustom
summary: 实现了一个侧边栏日历与倒计时卡片，包含以下核心功能：日历展示:显示当前日期（公历与农历）、星期、生肖、干支年份。动态生成当月日历网格，高亮当天日期。倒计时模块:距离除夕倒计时：实时计算并显示剩余天数（示例中为2026年2月16日）。进度条：分别展示本年、本月、本周的已过去进度（百分比）及剩余天数。农历支持:调用 chinese-lunar.js 库将公历转换为农历，格式化显示为“干支年+生肖年+农历月+农历日”。
abbrlink: 5ed2f1e6
date: 2026-01-16T15:47:07+08:00
---

{% note warning %}
注意：本文为早期 Hexo / Butterfly 魔改记录，本站现已迁移到 Astro，相关代码、路径或插件可能已经失效，仅作历史参考。
{% endnote %}

突然看到某个网站侧边栏有日历和倒计时，就研究了一下，抄下来了（🤭）
效果图：

<center><img src="https://pic.biss.click/image/b9819d72-3496-4204-9b7d-07d82e2e2f1b.webp" 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/ %}
