这次要做的 Web 前端实战是一个 Gitee 个人主页下的贡献图(在线 Demo),偶尔做一两个,熟悉熟悉 JS 以及 jQ。整体来说这个案例并不难,主要是控制第一个节点以及最后一个节点处于星期几;且必须保持365个节点。
整体的布局是上下,顶部一个 div、下面一个 div。下面的 div 是左右布局,左边是星期、右边是贡献节点。一列贡献节点一共有7个(不一定每一列都是7个)。星期要与每一行贡献节点保持平行。左右布局的高度保持一致,因此,每一个贡献节点的宽和高就取自布局的高/7
。
<div id="cb-chart"> <div class="top-bar"> <div class="occupation"></div> <div class="months"></div> </div> <div class="bottom"> <div class="left-side"> <div class="week">周一</div> <div class="week">周四</div> <div class="week">周日</div> </div> <div class="right-side"></div> </div> </div>
div.top-bar
和div.bottom
两个容器内的元素都是水平方向排列。div.top-bar
的高度占整个div#cb-chart
的 20%;div.bottom
自然就是 80%。
#cb-chart { width: 940px; height: 140px; } #cb-chart .top-bar { display: flex; height: 20%; width: 100%; } #cb-chart .bottom { display: flex; height: 80%; width: 100%; }
星期与贡献节点是左右排列在整个容器的,且星期占整个容器的 8%:
#cb-chart .bottom .left-side { width: 8%; height: 100%; } #cb-chart .bottom .right-side { width: 92%; height: 100%; }
星期下的所有元素要均匀分配整个div.left-side
的纵向空间:
#cb-chart .bottom .left-side { /* ...... */ display: flex; flex-direction: column; justify-content: space-between; }
div.right-side
中的所有元素(贡献节点)都是从上到下排列,若超出容器的空间时自动换行,左右之间的元素要保持垂直水平对齐:
#cb-chart .bottom .right-side { /* ...... */ display: flex; flex-wrap: wrap; justify-content: flex-start; flex-direction: column; align-content: center; align-items: center; }
下面两张图显示了顶部容器最左边有一个不显示的且占了空间的元素,主要目的是用来隔开星期剩余的空间,使顶部容器的每一个月份能够完美对齐下面的贡献节点。
因此在div.top-bar
中加了一个空元素<div class="occupation"></div>
:
#cb-chart .top-bar .occupation { width: 8%; }
div.left-side
的宽度 8%,所以它也是 8%;而div.months
自然就占整个容器的 92%。
#cb-chart .top-bar .months { font-size: 12px; height: 100%; width: 92%; display: flex; flex-direction: row; flex-wrap: nowrap; align-content: center; justify-content: space-between; align-items: center; }
这个就是每一个贡献节点元素的样式,宽高必须要经过计算之后才可以给出来,节点的宽和高取自div.bottom 的高/7
。使用 CSS var() 函数代替固定的值:
#cb-chart .bottom .right-side .point { width: var(--point-size); height: var(--point-size); border: 1px solid #FFF; box-sizing: border-box; background-color: white; }
在开始之前要给div.right-side
处添加大于365个的贡献节点的 div 元素:
function drawChartGrid() { let $el = $("#cb-chart .bottom .right-side"); $el.css("--point-size", `${ $el.height() / 7 }px`); for ( let h = 0; h < 54; h++ ) { for ( let v = 0; v < 7; v++ ) { $el.append(`<div class="point"></div>`); } } }
这些贡献度节点的数量是大于365个的,并且每一天的星期数都是有变化的,所以要计算今天是星期几,以及去年的今天是星期几,这样才可以定位起始节点和结束节点。
从起始节点开始,循环到结束节点,这之间就是准确的节点数量,一共365个。使用 $().slice(start, end)
函数循环,循环对象是div.right-side
下的所有div.point
。
函数需要两个变量来控制循环,起始和结束。起始就是去年的今天的星期是几?在 JS Date() 对象中,索引值 1 代表星期一;索引值 0 代表星期日。由于贡献节点的索引值是从 0 开始的,所以,索引值 0 代表星期一,6 代表星期日。所以,要进行计算:
星期 | Date 中的索引值 | 贡献节点索引值 |
---|---|---|
星期一 | 1 | 0 |
星期二 | 2 | 1 |
星期三 | 3 | 2 |
星期四 | 4 | 3 |
星期五 | 5 | 4 |
星期六 | 6 | 5 |
星期日 | 0 | 6 |
查找规律,除星期日以外,Date 中的索引值与贡献节点的索引值总是相差 1。所以,只需要 Date 中的索引值(weekIndex)减去 1 就知道slice()
函数从哪里开始。
特殊情况星期日:当weekIndex - 1 < 0
时,说明是星期日,函数要从索引值 6 开始。
function drawPoints() { let nowDate = new Date(); let oldDate = new Date(`${ nowDate.getFullYear() - 1 }-${ nowDate.getMonth() + 1 }-${ nowDate.getDate() }`); let weekIndex = oldDate.getDay(); let start = weekIndex - 1 < 0 ? 6 : weekIndex - 1; let end = start === 6 ? 372 : 365 + weekIndex; $(`#cb-chart .bottom .right-side .point`).slice(start, end).each((i, el) => { // ...... }); }
节点的颜色根据贡献的数量来设置:
function setPointColor(el, number) { if ( number > 0 && number <= 5 ) { $(el).addClass("a-type-point"); } else if ( number > 5 && number <= 10 ) { $(el).addClass("b-type-point"); } else if ( number > 10 && number <= 15 ) { $(el).addClass("c-type-point"); } else if ( number > 15 ) { $(el).addClass("d-type-point"); } else { $(el).addClass("e-type-point"); } }
函数要获取当前循环的贡献节点的 DOM 对象,以及这个节点对应的贡献数量。函数在下面进行调用:
function drawPoints() { // ...... let data = getPointsData(oldDate); $(`#cb-chart .bottom .right-side .point`).slice(start, end).each((i, el) => { setPointColor(el, data[i].number); }); }
这里出现了一个
getPointsData
函数,暂时不用管,它是随机生成数据的函数。
目前为止的效果如上图所示。
其余功能就不过多赘述,直接看源码理解即可,非常简单。Gitee 仓库-本案例源码