前言
团队需要基于Jira Issue做一些甘特图,方便进行工作排期,工作统计等。上网搜了一波感觉都不太符合预期,因此决定结合antd自己开发一个gantt。
废话不多说,先来看看效果:
甘特图有以下几个优点:
- 右侧甘特图是根据时间用svg绘画,没有依赖其他工具,可以自定义更加丰富的功能;
- 依赖antd,可以用antd 组件自定义更丰富功能,如气泡组件,form表单,tree组件;
- 用原生css,支持左右缩放
总的来说,因为主要功能没有依赖其他工具,可塑性强~可以用js实现更加丰富的功能~
背景
正式开始之前先介绍一下frappe-gantt, frappe-gantt是纯js绘制甘特图的开源项目。本次的甘特图绘制便是参考了该开源项目进行开发的,感兴趣的同学可以继续往下看,阅读完这遍文章除了可以从零学习开发甘特图,还可以对css有更深刻的了解。
布局
布局分析
甘特图分成左右两块,左侧展示数据,右侧排期可视化
左右两边的内部又分别分成上下两块,现需要:
- 【A】,【B】需要公用一个纵向滚动条
- 【a1】,【b1】各自吸顶不随滚动条移动
- 【B】拥有一个横向滚动条,【b1】,【b2】随横向滚动条向左右移动 效果如下:
代码实现
首先1
【A】,【B】需要公用一个纵向滚动条,第一反应是flex布局,【A】固定宽度,【B】flex: 1
。
.App {
display: flex;
height: 800px;
overflow: hidden;
overflow-y: scroll;
}
.A {
width: 300px;
border-right: 1px solid #000;
}
.B {
overflow-x: scroll;
width: 0; // 注意!!这里必须设置width:0,否则当B横向超出时,A无法正常展示宽度
flex: 1;
}
接着来看看2
【a1】,【b1】各自吸顶不随滚动条移动,这里建议使用 postion:sticky
Sticky positioning is a hybrid of relative and fixed positioning. The element is treated as relative positioned until it crosses a specified threshold, at which point it is treated as fixed positioned.
设置了sticky的元素,在屏幕范围时该元素的位置并不受到定位影响(设置是top、left等属性无效),当该元素的位置将要移出偏移范围时,定位又会变成fixed,根据设置的left、top等属性成固定位置的效果。
因为后面b1要随横向滚动条滚动,假如使用position: absolute
,则无法实现随滚动条滚动~来看看代码:
.a1,
.b1 {
height: 60px;
border-bottom: 1px solid #000;
position: sticky;
background-color: #fff;
top: 0;
}
最后最重要的3
【B】拥有一个横向滚动条,【b1】,【b2】随横向滚动条向左右移动, 认真看完前面两点的同学可能说,直接给B增加overflow-x: scroll
,不就可以了么,天真的我也是这样以为的😭😭
当给B加上overflow-x: scroll时,发现b1不吸顶🌚🌚🌚,再认真看看文档
父级元素不能有任何overflow:visible
以外的overflow设置,否则没有粘滞效果。因为改变了滚动容器(即使没有出现滚动条)。
因此,为了能够实现这个布局,只能加入js的辅助【假如有更好的方法可以通过评论告诉我~毕竟能够通过css解决的尽量用css解决😭】
去掉之前的.container overflow-y: scroll
,由js控制A,B的滚动。
const leftRef = useRef<HTMLElement>()
const rightRef = useRef<HTMLElement>()
const scrollLock = useRef({
isRightScroll: false,
isLeftScroll: false,
})
useEffect(() => {
// 监听右侧滚动,右侧滚动,左侧跟随滚动
rightRef.current?.addEventListener('scroll', function (e) {
// 当左侧不在滚动时
if (!scrollLock.current.isLeftScroll) {
scrollLock.current = {
...scrollLock.current,
isRightScroll: true,
}
leftRef.current?.scroll({
top: e.target?.scrollTop,
})
}
scrollLock.current = {
...scrollLock.current,
isLeftScroll: false,
}
})
// 监听左侧滚动,左侧滚动右侧跟随滚动
leftRef.current?.addEventListener('scroll', function (e: any) {
// 当右侧不在滚动时
if (!scrollLock.current.isRightScroll) {
scrollLock.current = {
...scrollLock.current,
isLeftScroll: true,
}
rightRef.current?.scroll({
top: e.target?.scrollTop,
})
}
scrollLock.current = {
...scrollLock.current,
isRightScroll: false,
}
})
}, [])
CSS实现双屏拖拽
如何纯CSS实现双屏拖拽呢?先来看看效果
css实现拖拽效果,是通过resize: horizontal;
属性, horizontal
如果一个block元素的
overflow
属性被设置成了visible
,那么resize
属性对该元素无效。
但是由于a2区域不需要滚动条,所以需要设置一个撑开a2且具有 overflow: scroll
的resize元素,真正的内容则放在一个position: absolute
的元素中。
<div className="a2">
<div className="resize" style={{ width: "500px" }}></div>
<div className="realContent">a2</div>
</div>
.resize::-webkit-scrollbar {
width: 10px;
height: inherit;
}
.resize {
position: sticky;
top: 0;
min-width: 280px;
max-width: 900px;
height: 900px;
overflow: scroll;
background: red;
opacity: 0;
resize: horizontal;
}
.realContent {
position: absolute;
top: 0;
left: 0;
width: calc(100% - 10px);
margin: 60px 10px 0 0;
z-index: -1;
}
.realContent::-webkit-scrollbar {
width: 0 !important;
}
大费周章的写完了布局如何实现,希望小伙伴能够都耐心的看完~毕竟在开发中为了研究如何用纯css实现布局,确实花了许多时间,~
左侧antd tree 实现按需加载
实现了上述的布局之后,后面除了甘特图的实现,其余的都可以使用antd组件自由发挥了
右侧的数据展示并没有使用antd table,因为想使用antd tree的展开异步加载 onLoadData,所以通过栅格布局将antd tree做成table的样式
假如没有异步加载子节点数据需求的同学,也可以直接用antd table组件实现该功能~
好奇如何将antd tree变成antd table请留言告诉我吧~
右侧svg绘画gantt将在下篇分享~