初衷与思路

想做在Hugo做一个类似Github那样的每日提交热度图,找到了一个很火的项目:

wa0x6e/cal-heatmap

思路:使用rss的数据,即 [domain]/index.xml为数据源,使用纯JS前端做逻辑。

最终效果:

endeffect

于是,开搞!

引入文件

在themes/xxx/layouts/partials/extend_head.html 尾部插入代码:

{{- if .IsHome }}
{{- /* cal-heatmap */}}

<script src="https://d3js.org/d3.v7.min.js"></script>

<script src="https://unpkg.com/cal-heatmap@4.0.0-beta.9/dist/cal-heatmap.min.js"></script>

<link rel="stylesheet" href="https://unpkg.com/cal-heatmap@4.0.0-beta.9/dist/cal-heatmap.css"></script>

{{/* tooltip */}}

<script src="https://unpkg.com/@popperjs/core@2"></script>

<script src="https://unpkg.com/tippy.js@6"></script>

{{- end }}

放在首页就做个IF判断,上部分是cal-heatmap依赖,下部分是鼠标移到小格子显示的tooltip依赖。

编写主文件

在static/js 新增主逻辑文件cal-heatmap.js,更新看本人的github项目

/*结果变量*/
const weekNum = 8;
const resultObj = {};
const resultList = [];

/*获取index.xml页面的数据*/

//补位0
function Appendzero(obj) {
  if (obj < 10) return "0" + "" + obj;
  else return obj;
}

if (window.XMLHttpRequest) {
  xhttp = new XMLHttpRequest();
} else {
  // IE 5/6
  xhttp = new ActiveXObject("Microsoft.XMLHTTP");
}

var localurl = "/index.xml"; //ssr地址
xhttp.overrideMimeType("text/xml");
xhttp.open("GET", localurl, false);
xhttp.send(null);
xmlDoc = xhttp.responseXML;

//遍历元素
var rootEle = xmlDoc.getElementsByTagName("channel")[0]["children"];
for (var key in rootEle) {
  var element = rootEle[key];
  if (element.nodeName == "item") {
    var child = element["children"];
    var title = child[0];
    var pubDate = child[2];
    if (pubDate.textContent == "" || title.textContent == "") {
      continue;
    }
    var date = new Date(pubDate.textContent);
    var dateFormat =
      date.getFullYear() +
      "-" +
      Appendzero(date.getMonth() + 1) +
      "-" +
      Appendzero(date.getDate());
    if (resultObj.hasOwnProperty(dateFormat)) {
      resultObj[dateFormat]["num"]++;
      resultObj[dateFormat]["postTitles"] += "," + title.textContent;
    } else {
      resultObj[dateFormat] = {};
      resultObj[dateFormat]["num"] = 1;
      resultObj[dateFormat]["postTitles"] = title.textContent;
    }
  }
}
//遍历obj,放入list
for (var key in resultObj) {
  var val = resultObj[key];
  var tmpJson = { date: key, value: val["num"], title: val["postTitles"] };
  resultList.push(tmpJson);
}
// console.log("HeatMap处理后的结果", resultList);

/*获取数据*/

//获取上几个星期的天
function getLastNWeeksDate(n) {
  const now = new Date();
  return new Date(now.getFullYear(), now.getMonth(), now.getDate() - 7 * n);
}
//获取这星期的周一
function getMonday(d) {
  d = new Date(d);
  var day = d.getDay(),
    diff = d.getDate() - day + (day == 0 ? -6 : 1); // adjust when day is sunday
  return new Date(d.setDate(diff));
}
var lastnweekday = getLastNWeeksDate(weekNum - 2);
var firstday = getMonday(lastnweekday);

const cal = new CalHeatmap();

//一年
// cal.paint({
//   theme: "dark",
//   domain: { type: "month" },
//   subDomain: { type: "day", label: "D", sort: "asc" },
// });

//一月
// cal.paint({
//   theme: "dark",
//   domain: {
//     type: "month",
//     label: {
//       text: null, //不显示标签
//     },
//   },
//   range: 1,
//   subDomain: { type: "day", label: "D", sort: "asc" },
// });

/*深色与明亮主题初始值判断*/
var isDark = document.body.className.includes("dark");
// console.log("是否深色主题", isDark);
var hlDate = new Date().toLocaleDateString();
var hlArr = hlDate.split('/');
var dateFormat = hlArr[0] + "-" + Appendzero(hlArr[1]) + "-" + Appendzero(hlArr[2]);
var realHLDate = new Date(dateFormat);

/* 参数 */
var options = {
  animationDuration: 200,
  theme: isDark ? "dark" : "light",
  verticalOrientation: true,
  date: {
    start: firstday, //开始时间为上8-2个周的周一
    highlight: [realHLDate],
    locale: { weekStart: 1 }, //周一为第一天
  },
  domain: {
    type: "week",
    label: {
      position: "right",
      text: null, //不显示标签
    },
  },
  range: weekNum,
  subDomain: {
    width: 12,
    height: 12,
    type: "day",
    // label: "D", //D是显示日期的日;null是不显示;回调函数是自定义
    label: function (timestamp, value) {
      return value;
    },
    sort: "asc",
  },
  data: {
    type: "json",
    source: resultList,
    x: "date",
    y: "value",
  },
  scale: {
    color: {
      range: ["#eea2a4", "#5c2223"], //[浅色,深色]
      interpolate: "hsl",
      type: "linear",
      domain: [0, 5], //文章数阈值:[min,max]
    },
  },
};

/*深色与明亮主题切换监听*/
document.getElementById("theme-toggle").addEventListener("click", () => {
  location.reload();// 由于没有重新渲染的函数,只能刷新界面
});

/*渲染*/
cal.paint(options);

/*初始点击的数据*/
let listObj = {}
function initClickUrls() {
  fetch("/index.xml")
    .then((res) => res.text())
    .then((str) => new window.DOMParser().parseFromString(str, "text/xml"))
    .then((data) => {
      let ls = data.querySelectorAll("channel item");
      listObj = {};
      ls.forEach(element => {
        let eleCollect = element.children;
        let dateStr = eleCollect.item(2).textContent;
        let url = eleCollect.item(3).textContent;
        let dateTs = Date.parse(dateStr);
        var date = new Date(dateTs);
        var ymd = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
        if (listObj.hasOwnProperty(ymd)) {
          listObj[ymd]['url'].push(url);
        }
        else {
          listObj[ymd] = { 'date': ymd, 'url': [url] };
        }
      });
      console.log(listObj)
    });
}
initClickUrls();

//事件处理
cal.on("mouseover", (event, timestamp, value) => {
  var date = new Date(timestamp);
  var dateFormat = Appendzero(date.getMonth() + 1) + "/" + Appendzero(date.getDate());
  var str = '周' + '日一二三四五六'.charAt(new Date(timestamp).getDay());
  var tips = "";
  if (value == null) {
    tips = str + " " + dateFormat + " , 懒虫!";
  }
  else {
    tips = str + " " + dateFormat + " , " + value + " 篇";
  }
  tippy(event.target, {
    placement: "top",
    content: tips,
  });
});
cal.on('click', (event, timestamp, value) => {
  var date = new Date(timestamp);
  var ymd = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate();
  var dateObj = listObj[ymd];
  if (dateObj) {
    var urlList = dateObj.url;
    if (urlList && urlList.length > 0) {
      var locationHref = urlList[Math.floor(Math.random() * urlList.length)];
      location.href = locationHref;
    }
  }
});
多种样式,如显示1年,1个月,2个月等自行修改代码,因为我的主题适合规规矩矩的,所以使用8周为时间单位。

(可忽略) 在static/css 新增主逻辑文件footer-home.css,更新看本人的github项目

.first-entry {
    position: relative;
    display: flex;
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
    min-height: 320px;
}

.cal-heatmap-container {
    display: flex;
    flex-direction: row;
    justify-content: flex-end;
    align-items: center;
    width: max-content;
}

引入主文件

在themes/xxx/layouts/partials/extend_footer.html 尾部插入代码:

{{- if .IsHome }}
{{- /* cal-heatmap */ -}}
<script type="text/javascript" src="js/cal-heatmap.js"></script>
<link rel="stylesheet" href="css/footer-home.css">
{{- end }}

大功告成~ 如有疑问可留言!

后续问题

  • dark/light主题切换问题
  • tooltip显示
  • 点击日期进入相关post页面