⼀起来聊聊table组件的固定列
现有Web页⾯上,table⼀般⽤于统计数据、列表书记等信息的展⽰。但是当table包含⼤量的信息需要列出展⽰时,往往对table添加滚动条来展⽰更多的数据。突然想到要研究下table组件也是因为最近碰到table数据⼤量展⽰的问题,寻觅了⼀些⽐较好的解决办法,就⽬前来说,个⼈已知最好的应该就是。它通过对表格前后列进⾏固定,中间滚动的交互⽅式可以很好的满⾜个⼈需求,这种交互对于⼤量表格数据和关键信息的展⽰⾮常友好,下⾯我们就来聊聊包含⼤量数据和列数的表格展⽰优化。
表格功能
⾸先需要对表格的⼤致功能有⼀个⼤概的了解。Ant-design上对表格的使⽤说明如下:
当有⼤量结构化的数据需要展现时;当需要对数据进⾏排序、搜索、分页、⾃定义操作等复杂⾏为时。
通常来说,表格的主要⽤途就是数据的展⽰,然后还附带⼀些其他的功能来满⾜对数据的整理、筛选等。
表格设计
表格⼀般包括表头、表尾,内容对应具体的⾏列,每⼀个数据占据⼀个单元格,如下图所⽰。
根据HTML中表格的标签属性,我们对表格进⾏拆分:
表头
表尾
⾏
列
单元格
对应常⽤的功能项包括:
选择等操作
筛选和排序
展开数据
表格⾏列合并
树形数据
固定表头、表尾
固定列
固定头和列
编辑
…
⼤量数据的表格交互优化
对于包含⼤量数据和列数的表格,添加滚动条是⼀种常见的⽅法,⽤来提⽰表格包含更多的内容。但是,这种⽅法往往不够简洁,并且不容易展⽰关键的信息。
滚动条往往太过笨重,在视觉上喧宾夺主,因此,现代操作系统已经开始简化它的外观,当⽤户不与可滚动的元素交互时,滚动条就会被隐藏。(《CSS揭秘》34-滚动条提⽰)
对于⼤量数据的表格,更好的⽤户体验优化主要包含以下两点:
1. 更优雅的⽤户体验模式——以阴影来提⽰更多的内容 在css揭秘中有提及到:“Google Reader的⽤户体验设计师到了⼀种⾮常优
雅的⽅式来做出和滚动条提⽰类似的提⽰:当侧边栏的容器还有更多的内容时,⼀层淡淡的阴影会出现在容器的顶部或者底部”。 对
于⼤量数据的表格,我们可以以类似的⽅式来提⽰⽤户表格包含更多的内容,需要滚动。
2. 关键信息展⽰——固定列&表头 对于表格信息来说,往往有些信息是重要的,需要⼀⽬了然的看清楚,⽽不是通过滚动才能看到⽤户
需要的信息。将关键的表格列进⾏固定,⽽次要信息放置在可滚动查看的容器中,可以突出表格信息的重要性,同时,对于⽅便⽤户更好的在当前窗⼝进⾏交互,⽽不需要更多的操作。
对于以上两种交互的优化,给出了很好的⽰例。
优化的实现
对于固定头和列的表格数据交互⽅式,现有的组件库均有对应的实现,⽐较常见的对应有:
React:
Vue:,
Regular:
那么如何实现上述表格的交互优化呢,综合以上三种组件库对于table组件的实现⽅式来看,具体包括以下⼏个⽅⾯(以下⽂中⽰例代码使⽤vue实现)。
布局
在考虑表头和列进⾏固定时,我们⾸先需要考虑的是如何对表格进⾏布局来同时满⾜上下和左右的滚动。
如上图所⽰,蓝⾊框框部分将表格分为固定的表头和内容,内容区域可以上下滚动;红⾊框框部分将表格分类固定的列和中间列,中间部分可以左右滚动。从⽰意图可以发现,两部分有⼀部分内容交叉。 在布局上,主要实现⽅式为以下两种:
1. 如果依据左右滚动布局,那么整个表格分为左,中,右三部分,中间overflow左右滚动。那么,如果同时满⾜表头固定后,body上下
滚动,需要js事件监听中间部分的上下滚动,同时对左右表格的body部分进⾏滚动。见
2. 如果依据上下滚动布局,那么,整个表格将分为两⼤部分,thead和tbody,内容部分溢出后滚动。那么,如果需要同时满⾜中间部分
进⾏左右滚动,需要在tbody左右滚动时,同时控制thead的中间进⾏滚动,需要js监听tbody的scroll事件,对thead进⾏滚动。见
这两种⽅式均需要通过js监听scroll事件,来满⾜另⼀部分的滚动事件,⽰例的demo⽐较简陋。对⽐这两种⽅式,可以说实现上都差不多,现有组件库的实现均为第⼀种⽅式。采⽤这⼀类布局的共同点和优势如下:
将表头和内容分开为两部分,便于控制表头固定。
通过布局满⾜上下或者左右的滚动,通过js控制另⼀⽅向的滚动,相对于完全通过js控制滚动更有优势。
需要特别注意的⼀点是,中间内容的表格需要包含左右固定的两列,⽽不是完全拆分,左右固定的列仅仅是通过固定布局覆盖现有的数据。这样做的优势是保证在中间部分滚动的时候,滚动条属于整个table表格⽽不是中间部分,给⽤户视觉上的整个表格滚动的交互体验,这样也保证了⼀个⽅向上的完全滚动。
左右滚动布局需要在body滚动的同时控制左侧固定列和右侧固定列同时滚动,相对于上下滚动布局仅仅需要控制表头内容左右滚动来说,减少⼀次计算控制。本以为布局2在数据量⼤的情况下,相对布局⼀发⽣抖动的可能性更⼤,但是在5000⾏数据测试下(仅测试chrome),发现滚动时,两者相差不⼤。
固定⾏列对齐
由于表格的表头、列固定⽅法采⽤的是从table中拆分的⽅法,固定的表格部分和其余部分可能不⼀致。同时,表格的内容不固定的情况下,其布局也是⽐较难以预测的,浏览器往往会根据内容对表格列宽进⾏调整。
因此,我们在真正使⽤表格进⾏数据展⽰时,由于数据的不可控性,展⽰出来的效果可能和我们预计的不⼀样,如下图所⽰。
::表格⾏不对齐::
::表格列不对齐:: 对于这两种情况,解决⽅式如下:
1. 列不对齐,给表格每⼀列固定宽度,通过定宽的⽅法来控制表格。
设定table的布局⽅法为table-layout: fixed。 table-layout的默认值是auto,其⾏为模式被称作⾃动表格布局算法。将表格的布局⽅
式设置为fixed不仅有利于页⾯的快速渲染,同时也让表格更可控。具体的规范见:
使⽤ “fixed” 布局⽅式时,整个表格可以在其⾸⾏被下载后就被解析和渲染。这样对于 “automatic” ⾃动布局⽅式来说可以加速渲染,但是其后的单元格内容并不会⾃适应当前列宽。任何⼀个包含溢出内容的单元格可以使⽤ overflow 属性控制是否允许内容溢出。
指定表格上每⼀列的宽度。设置宽度的⽅法有两种,其⼀是对每⼀个th指定width,另⼀种是使⽤col属性。 相对来说,col的⽅式和对每⼀个th设置宽度⽅式⽐,更优雅⼀点。我们还可以通过来对表格中的列进⾏组合,以便对其进⾏格式化。不过需要注意的⼀点是,⽬前的浏览器对于col和colgroup,很多属性已经被废弃了,⽬前⽀持度⽐较全⾯的仅span和width这两个属性,分别⽤于规定列组应该横跨的列数和列的宽度。
设定宽度和table-layout后,如果整个表格的宽度⼤于所有列设定的宽度,且所有列均设置宽度,那么会将多余的宽度按照设置的列宽度⽐例平均分配。如果只设置了部分列,那么平均分配到剩下未指定宽度的所有列中;如果整个表格宽度⼩于所有列的设定宽度,且所有列均设置宽度,那么表格会被列撑开。如果只设置了部分列,没有设置宽度的列会被压缩宽度。
2. ⾏不对齐,实现⽅式有三种。
如果表格中展⽰的数据不需要完全展⽰,可以指定表格⾏的⾼度,当表格数据溢出时,⾃动出现省略号来防⽌表格⾏左右不对齐的情况。这种⽅式适⽤于已知数据的,固定内容的表格数据展⽰。
对左右固定列的两个table,不仅仅包含固定列的数据,并且包含所有表格数据。这样表格⾏⾼依据于整个表格⼀⾏的数据,并且保持左、中、右数据的完全⼀致,不会出现左右⾏⾼不⼀致导致⾏不对齐。这种⽅式会导致html中拥有较多的冗余数据,不太利于语义化。
绘制时,计算⼀次中间body的表格⾏⾼度,根据中间⾏⾼来设定左右固定列的表格⾏⾼(Ant-design的实现⽅法)。这种⽅式减少了冗余数据,但是在初始js渲染时,可能需要更长的时间。
左右阴影提⽰
给table左右固定列均增加⼀个淡淡的阴影,可以很好的给⽤户以“这⾥还有更多数据”的提⽰,同时,阴影也让固定列的数据更有层次感。 例如通过给左右固定列增加box-shadow,可以增加阴影效果,实现css如下:
.table-fixed-left-scroll {
box-shadow: 6px 0 6px -4px rgba(0,0,0,.2);
}
.table-fixed-right-scroll {
box-shadow: -6px 0 6px -4px rgba(0,0,0,.2);
}
复制代码
就现有的⼤部分组件库来说,仅仅做了阴影的实现,当我们滚动时,这条阴影会⼀直停留在相同的位置。相对来说,更好的交互效果是和Ant-design的table⼀样,阴影会随着我们的滚动⽽⾃动出现,这样给⽤户的提⽰也就更加友好,同时可以增加transition动画来让左右阴影出现的更为⾃然。
实现⽅式 监听body区域scroll事件,通过判断scrollLeft以及整个整个body区域是否被overflow的宽度,来判断左阴影和右阴影。同时需要注意的是:在窗⼝resize的时候,需要对左右阴影进⾏重新计算。为了减少scroll事件时整个区域被overflow的宽度的计算(计算clientWidth会增加⼀次浏览器的重排),在整个窗⼝onload和resize成功后,需要对窗⼝进⾏⼀次计算,并将计算结果缓存。
/* onscroll */
handleBodyScroll() {
this.scrollValue = this.$refs.tableScroll.scrollLeft;
this.hasRight = this.scrollValue - this.leftScroll < 0;
this.hasLeft = this.scrollValue > 0;
},
/* onload & onresize */
setTableShadow() {
this.leftScroll = this.$refs.tableContent.clientWidth - this.$refs.tableScroll.clientWidth;
this.handleBodyScroll();
}
复制代码
更进⼀步的优化尝试
对于需要固定表头和表列的table,往往数据量都⽐较⼤。我们可以对现有的实现⽅式做更进⼀步的优化尝试。
使⽤tranfrom来代替设置scrollLeft
考虑,⼀般来说,scrollLeft会引起⼀次重排,⽽transfrom仅仅是⼀次重绘,同时我们可以使⽤transform3D使⽤GPU来加速浏览器的渲染。那么,是不是可以考虑在scroll事件中,使⽤transform来代替scrollLeft的设置呢? 在这篇⽂章中,有提及到实现⼀个优化的table 组件使⽤下⾯两点来优化onScroll 触发的频率和渲染的速度跟不上造成的抖动问题,他的解决办法如下:
⽤ transform: translate3D 代替 top 与 left ,因为 top/left 会导致回流,⽽ translate 只产⽣重绘,性能会更好,另外
translate3D ⾛的是 3D, 在⼿机浏览器器上会 GPU 加速。
onScroll 触发的频率和渲染的速度会存在跟不上的情况,所有这⾥最好是⾃⼰实现⼀个滚动条,在 Table Body 上监听 onWheel 事件,在滚动条上监听 onMouse* 事件。 在⾃⼰实现滚动条的时候需要注意的是,在 Mac 的 chrome 上,左右滑动的时候会触发浏览器的上⼀页和下⼀页功能,所以这⾥的
事件冒泡要处理好(本来想⼀个开源的滚动条轮⼦,发现有好多组件这个问题没有处理好,所以就⾃⼰写了)。
为了验证transform和scroll实际效率的对⽐,使⽤5000⾏数据对之前的demo进⾏了scroll抖动的测试,发现两种布局⽅式对应的效率提升并没有很明显。
::左右布局-scroll::
::左右布局-transform3D::
::上下布局-scroll::
::上下布局-transform3D::
从以上四个图可以对⽐看出,tranfrom3D和scroll的性能差距并没有很⼤。我们还可以通过 对两者进⾏两者的性能分析,同样得到transfrom3D和scroll对滚动性能的影响并没有相差很⼤这个结论。
减少scroll事件中的dom操作
本⽂中的Demo只是简单的⽰例,为了更好的对⽐table的scroll性能,通过对iview和element两个组件
库的table均进⾏了5000⾏数据下table滚动的测试,发现两者均有⼀定的抖动(都有做防抖动处理,不够明显)。 ::iview-table-scroll抖动::
::element-table-scroll抖动::
查看两个组件库的源码,发现两者均在scroll事件中执⾏了较多的逻辑判断,例如iview在scroll中进⾏了column是否存在的判断,时,每次渲染都对操作那⼀列进⾏了重新渲染计算,element在scroll时,对左右和上下的滚动都进⾏了判断计算,这些也都影响滚动的性能。由于onscroll的⾼频触发,尽量减少scroll事件中对dom的计算和操作,减少浏览器重排和重绘。
优化滚动事件
对于onscroll,onresize等这⼀类⾼频触发的事件,如果事件中涉及到⼤量的位置计算、DOM 操作、元素重绘等⼯作且这些⼯作⽆法在下⼀个 scroll 事件触发前完成,就会造成浏览器掉帧。加之⽤户⿏标滚动往往是连续的,就会持续触发 scroll 事件导致掉帧扩⼤、浏览器 CPU 使⽤率增加、⽤户体验受到影响。
对⽤户来说,平滑的滚动往往能带来很好的交互效果,优化滚动事件往往有以下三种⽅法:
防抖(Debouncing):把多个操作合并为⼀个,即在⼀定时间内,规定事件出发的次数。
element表格横向滚动条节流(Throttling):只允许操作在⼀定时间内执⾏⼀次,只有当上⼀次操作执⾏后过了你规定的时间间隔,才能进⾏下⼀次。这种⽅法往往运⽤于图⽚懒加载的的优化。
使⽤ rAF(requestAnimationFrame)触发滚动事件:在页⾯重绘之前,通知浏览器进⾏操作。 ⼀般来说,现有的table组件中,⼀般采⽤第⼀种⽅式来对scroll事件进⾏处理,通过约束⼀定时间内发⽣的次数,来因为⽤户过快操作导致的scroll请求。
使⽤纯CSS实现阴影⾃动提⽰
在《CSS揭秘》⼀书的第34⼩节滚动提⽰中(具体⽂章可见:),对这种⽅法进⾏详细的阐述,其原理实现是通过设置两层背景来得到阴影,或者通过设置伪元素和定位来实现,⽅法实现如下:
两层背景:play.csssecrets.io/scrolling-hints
伪元素和定位: 对于table组件,实现css如下:
.tablebackground {
background: linear-gradient(white 15px, hsla(0,0%,100%,1)) 100px 0 / 15px 300px,
radial-gradient(at left, rgba(0,0,0,.2), transparent 80%) 100px 0 / 10px 200px,
linear-gradient(to left, white 15px, hsla(0,0%,100%,1)) right / 110px 300px,
radial-gradient(at right, rgba(0,0,0,.2), transparent 70%) 390px / 15px 300px;
background-repeat: no-repeat;
background-attachment: local, scroll, local, scroll;
}
复制代码
实现原理 通过这个属性,它包含fixed、local和scroll三种取值。
fixed:此关键字表⽰背景相对于视⼝固定。即使⼀个元素拥有滚动机制,背景也不会随着元素的内容滚动。
local:此关键字表⽰背景相对于元素的内容固定。如果⼀个元素拥有滚动机制,背景将会随着元素的内容滚动, 并且背景的绘制区域和定位区域是相对于可滚动的区域⽽不是包含他们的边框。
scroll:此关键字表⽰背景相对于元素本⾝固定, ⽽不是随着它的内容滚动(对元素边框是有效的)。
具体实现为:通过构建两层背景,⼀层⽤于⽣成阴影,使⽤scroll属性,默认和内容保持在原位;另⼀层为⼀个⽤来遮挡阴影的⽩⾊矩形,使⽤local属性,相对于可滚动区域位置固定,这样它就会在滚动在最左侧或者右侧时盖住阴影,滚动时跟着滚动,露出阴影。 同时,使⽤线性渐变的遮罩层可以让滚动时阴影出现的更为平滑,具体的实现效果请看demo。
这种纯CSS的实现很好的避免了通过js监听事件来对dom节点进⾏操作,更好的优化了性能,但是这种写法依然存在⼀定的问题:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论