前言
产品要求实现一个类似github上的提交代码活跃图的日历格子,虽然觉得会很麻烦,但是需求到我手里,就得做了。
我使用了CSS的Grid布局,实现了展示上的需求,如下图所示,这会儿我还美滋滋的,觉得接下来会很顺利,没想到接下来和交互相关的部分,我陷入了深思
html结构
ts
<div className="contaniner">
<ul className="weeks">
{weeks.map((item, index) => {
return <li key={index}>{item}</li>;
})}
</ul>
<div>
<ul className="graph">
{infos.map((item, index) => {
const classes = {};
return (
<Tooltip title={item?.year + '-' + item?.month + '-' + item?.date}>
<li
data-id={index}
onMouseDown={handleOnMouseDown}
onMouseUp={handleOnMouseUp}
data-level={item?.level}
key={index}
// style={pseElementStyle}
className="li-day li-new"
></li>
</Tooltip>
);
})}
</ul>
<ul className="months">
{monthBar.map((item, index) => {
return (
<li key={index} className="li-month">
{item.month}
</li>
);
})}
</ul>
</div>
</div>
CSS结构
less
.contaniner {
display: flex;
overflow-x: scroll;
max-width: 740px;
.weeks {
display: grid;
grid-template-rows: repeat(7, 28px); /*横向7列,21px宽*/
margin: 20px 8px 5px 20px;
}
.graph {
display: grid;
grid-template-columns: repeat(auto-fill, 28px); /*竖向12列,21px宽*/
grid-template-rows: repeat(7, 28px); /*横向7列,21px宽*/
padding-inline-start: 0px;
grid-auto-flow: column; /*生成7*12的格子后,设置为竖向排布*/
margin: 20px 20px 5px 20px;
}
.li-day {
width: 20px;
height: 20px;
list-style: none;
margin: 4px;
border-radius: 4px;
}
.li-has-no-config {
width: 20px;
height: 28px;
overflow: auto;
}
.select-rest {
border: 1px solid #37caec;
}
.today-rest {
border: 1px solid #255de6;
color: #255de6;
}
.today-default {
border: 1px solid #255de6;
color: #255de6;
}
.today-config {
border: 1px solid #255de6;
color: #fff;
}
.click {
border-radius: 4px;
border: 1px solid #37caec;
background: #255de6;
}
.today-click {
background: #255de6;
color: #ffff;
}
.graph li[data-level='1'] {
background-color: #f5f6f7;
}
.graph li[data-level='2'] {
background-color: rgba(37, 93, 230, 0.3);
}
.graph li[data-level='3'] {
background-color: rgba(37, 93, 230, 0.7);
}
.months {
display: grid;
grid-template-columns: repeat(53, 28px);
grid-template-rows: 28px;
font-size: 12px;
color: #aaa;
padding-inline-start: 0px;
margin: 5px 20px 5px 20px;
}
.li-month {
display: inline-block;
}
}
产生的问题
接下来,我要在此基础上实现以下的两种交互:
就是既保持底部的grid格子的颜色,在鼠标点击,并一直按下的情况下,选中一些格子,并且给选中的格子添加一些背景色或者是边框。
我现在的项目中用的样式的预处理器是less, 项目的框架是React
我思考实现上面的交互,考虑了两个方案,以下就从这两个方案来分析一下,为什么上面这个交互没有办法实现。
方案一
通过鼠标的点击事件开始和结束,去判断开始和结束的小格子的index,然后通过判断小格子是不是在开始和结束的index的范围内,就添加一个新的className的类型li-selected
css
.li-selected::before {
content: '';
position: absolute;
width: 20px;
height: 28px;
margin: 0;
background-color: rgba(55, 202, 236, 0.5); /* 添加的叠加层颜色 */
}
这种方法存在的问题是,从我在上面的截图,很清晰的看到,我要展示的是12个月份,由于布局的空间问题,还会有横向的滑动,用这种方法就会导致li-select
的样式在li-day
进行滑动的时候,就会和li-day
的元素错位,所以这种方法pass
方案二
既然我使用了grid,那我就直接用nth去表示吧,我写了如下的样式
css
.li-day:nth-child(n + 12):nth-child(-n + 21)::before {
content: '';
position: absolute;
width: 20px;
height: 28px;
margin: 0;
background-color: rgba(255, 0, 0, 0.5); /* 添加的叠加层颜色 */
}
上面的样式表示我给第12个到第21个的方块上方添加了一层红色的叠加,这时候考虑到我们鼠标点击的时候起始的结束的位置都是不确定的,所以,结合less, 我打算使用less中的变量:
less
@start-index: 12;
@end-index: 26;
.generate-nth-child-styles(@start, @end) when (@start < @end) {
.li-day:nth-child(n + @{start}):nth-child(-n + @{end}) {
// 样式定义
background-color: #ccc;
&::before {
content: '';
position: absolute;
width: 20px;
height: 28px;
margin: 0;
background-color: rgba(255, 0, 0, 0.5); /* 添加的叠加层颜色 */
}
}
}
// /* 调用样式生成函数 */
.generate-nth-child-styles(@start-index, @end-index);
现在我的问题是怎么动态的去给我的样式的开始和结束赋值,但是经过我的一番研究,存在以下矛盾:
- Less 中的变量是在编译时静态地解析的,这意味着它们在 Less 文件编译成 CSS 文件的过程中就已经确定了其最终的值,并不能在运行时动态地改变。
- 在React中使用内联样式是无法表示::before这样的伪类的
当然,我也在积极的尝试使用其他的方法,比如说,我尝试直接使用css,使用css里面的变量,但是css的变量在nth-child()中是不生效的。虽然说可以使用styled-components在内联样式里面表示伪类,但是为了程序的某一个小部分去替换less,我觉得不合理,所以我没有尝试这种方法
结尾
如果大家有其他的实现方式也可以在评论区给我积极评论,以上的内容就是我的一个思考的过程。