table表格组件预览地址 (展现官网会略卡,一开始容易加载不出来)css
目前暂时打算完成的功能vue
结构和api借鉴AntDesign的,git
<x-table
:columns="columns1"
:data="data">
</x-table>
复制代码
api设计:github
key
好用来确认他们的index
。{key:1,name:'JavaScript',price:80,year:12},
复制代码
data
里面的属性名。columns1:[
{text:'名字',field:'name'},
{text:'价格',field:'price'},
{text:'年份',field:'year'},
],
复制代码
效果如图 web
遗憾的是table
的头部没法只经过css去固定,这东西很是特殊。因而, 把这里分为两个部分,body
和header
。chrome
header
是一个崭新的table
,该table
组件里面只有<thead></thead>
,经过绝对定位覆盖body
的头部,以达到固定头部的目的。后端
<template>
<div>
<!-- 固定的头部,header部分-->
<table>
<thead>
</thead>
</table>
<!-- body部分-->
<table>
<tbody>
</tbody>
</table>
</div>
</template>
复制代码
首先固定头部确定须要一个最大高度,也就是maxHeight
。api
<x-table :columns="columns1"
:data="data"
//.....
maxHeight="300"
>
</x-table>
复制代码
这里的header部分的table
由于是没有body
的,两个table
对应的每一个格子的宽度不相同,就致使了不对齐的问题,因而就须要固定宽度。数组
在columns
传入的每条数据里面加入width
,意味着每列对应格子的宽度。浏览器
columns1:[
{text:'名字',field:'name',width:200},
],
复制代码
如今是如何控制格子的宽度,开始踩坑的时候我用js遍历去给格子.style.width
赋值,但这种作法是彻底不可行的。
colgroup
作的。
<colgroup>
<col v-for="(column,index) in columns" :key="index" :style="{width:`${column.width}px`}">
</colgroup>
复制代码
这里还要考虑checkBox
的影响,这在后面会说到。
而后就能够用绝对定位覆盖到上面。
position: absolute;
left: 0;
top: 0;
复制代码
固定最大高度,超出部分能够滚动
首先table
的外层须要包裹一层div
,用来控制最大高度。设置css:overflow:auto;
。`
columns1:[
{.......,fixed:'left'},
{........,fixed:'right'},
],
复制代码
全部fixed:'left'
的都放在左边,fixed:'right'
放在右边。 如今总体就能够分为三部分了。左列固定,中间滚动区域,右列固定。
收集三个部分的数组,而且在格子的table
里面遍历他们。
'收集函数'(){
let [left,right,main] = [[],[],[]]
this.columns.forEach(item=>{
[item.fixed].push(item)
})
this.fixedLeft = left.concat(main,right)
this.fixedRight = right.concat(main,left)
this.scrollArea = left.concat(main,right)
}
复制代码
用concat
对作一个拼接,这样子在外层div
包裹的时候能够直接用maxWidth
和overflow:hidden
截取显示的部分。
//左边固定
<table class='左边'>
<colgroup>
<col style="width: 60px">
<col v-for="(column,index) in fixedLeft" :key="index" :style="{width:`${column.width}px`}">
</colgroup>
<thead>
<tr>
//.....
<th v-for="column in fixedLeft" :key="column.field">
{{column.text}}
</th>
</tr>
</thead>
</table>
//中间滚动
<table class='滚动区域'>
<colgroup>
<col style="width: 60px">
<col v-for="(column,index) in fixedLeft" :key="index" :style="{width:`${column.width}px`}">
</colgroup>
<thead>
<tr>
//.....
<th v-for="column in fixedLeft" :key="column.field">
{{column.text}}
</th>
</tr>
</thead>
</table>
//右边固定
<table class='右边'>
<colgroup>
<col style="width: 60px">
<col v-for="(column,index) in fixedLeft" :key="index" :style="{width:`${column.width}px`}">
</colgroup>
<thead>
<tr>
//.....
<th v-for="column in fixedLeft" :key="column.field">
{{column.text}}
</th>
</tr>
</thead>
</table>
复制代码
须要注意的是table
的宽度会受外面div
包裹层的宽度影响。因此须要在开始就固定好table的宽度。而后给父级一个maxWidth
。
setMainWidth(){
let [width,$refs] = [getComputedStyle(this.$refs.table).width,this.$refs]
$refs.table.style.width = width
$refs.wrapper.style.width = this.maxWidth +'px'
//......
},
复制代码
由于考虑到固定列的同时还能固定头部,左右的结构和以前的大致相同
<div class="main">
<!-- 中间的头部部分-->
<table></table>
<!-- 中间的body部分-->
<table></table>
</div>
<div class="left">
<!-- 左边的头部部分-->
<table></table>
<!-- 左边的body部分-->
<table></table>
</div>
<div class="right">
<!-- 右边的头部部分-->
<table></table>
<!-- 右边的body部分-->
<table></table>
</div>
复制代码
这里固定左列和固定右列除了css样式外,还有些不一样的地方。
hover其中一部分其余的一块儿改变背景颜色
hoverChangeBg(index,e){
let typeName = {
mouseenter:'#FCF9F9',
mouseleave:''
}
this.$refs.trMain[index].style.backgroundColor = typeName[e.type]
if(this.fixedLeft.length>0){
this.$refs.trLeft[index].style.backgroundColor = typeName[e.type]
}
if(this.fixedRight.length>0){
this.$refs.trRight[index].style.backgroundColor = typeName[e.type]
}
},
复制代码
图画表示
'不须要展现滚动条的部分'{
&::-webkit-scrollbar{
display: none;
}
-ms-overflow-style: none;
scrollbar-width: none;
-ms-overflow-style: none;
overflow: -moz-scrollbars-none;
}
//兼容chrome,firefox和IE(?)和其余大部分浏览器。
复制代码
position:absolute;
right:0;
top:0;
复制代码
覆盖中间滚动区域的竖直滚动条。
'我是获取滚动条厚度的函数'(){
const scrollBar = document.createElement('div')
let style = {
height:'50px',
overflow:'scroll',
position:'absolute',
top:'-9999px',
width:'50px'
}
Object.keys(style).forEach(item=>{
scrollBar.style[item]=style[item]
})
document.body.appendChild(scrollBar)
this.scrollBarWidth= scrollBar.offsetWidth - scrollBar.clientWidth
document.body.removeChild(scrollBar)
}
复制代码
至于这个函数的兼容性问题,暂时没有考虑。
最麻烦的一部分,至今尚未彻底解决,事实上在elementUI上也略微有点瑕疵。先说下个人解决过程。
最开始的尝试(已放弃): 一开始使用mouserwheel
监听,但兼容性存在问题。
判断浏览器是否为火狐
const isFirefox = typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
复制代码
来监听mouserwheel
或火狐特立独行的DOMMouseScroll
。 固然,原生的监听须要在beforeDestroy
钩子里删除一下。
const mousewheel = function(element, callback,name) {
if (element && element.addEventListener) {
element.addEventListener(isFirefox ? 'DOMMouseScroll' : 'mousewheel', function(event) {
callback && callback.apply(this, [event,name]);
});
}
};
export default {
bind(el, binding,name) {
mousewheel(el, binding.value,name);
}
};
复制代码
大致想法是用deltaY
控制其余两部分的scrollTop
,然而火狐没有这东西(至关的蛋疼),须要一些库的支持。总之大致的写法就是相似下面的
'须要同步滚动的部分'.scrollTop += e.deltaY
复制代码
只这么写这样子有个问题,就是滚滑轮的时候目标元素不必定在滚动,多是父级或者window
,可是依然会触发mousewheel
事件。这样子就会出现大量移位的状况。 以后试了n种方法,
'须要同步滚动的部分'.scrollTop += e.deltaY
'wheel的那部分'.scrollTop += e.deltaY
复制代码
第二种方法: 使用原生的scroll
事件,经过scrollTop
来作同步
'须要同步滚动的部分'.scrollTop = scrollTop
复制代码
须要监听三个部分的scroll
事件,可是一旦其中一个触发了scroll
事件,就会修改其余两个的scrollTop
,以后又会触发其余两部分的scroll
事件。也就是说假设监听调用的都是同一个函数,那么滚一次就会调用三次这个函数。(在非Chrome浏览器上实际额外触发的次数更多,这也是滚动缓慢的缘由) 这种状况在除了chrome外的浏览器滚动十分缓慢。 目前尝试的两个方法:
addEventListener
和removeEventListener
。控制scrollTop
以前移除监听,以后再监听。hover
监听,只有hover
的区域能够触发scroll
。scrollGradient(part){
if(part!=='正在hover的区域')return
let position = {
left:[`tableLeftWrapper`,`tableMainWrapper`,`tableRightWrapper`],
main:[`tableMainWrapper`,`tableLeftWrapper`,`tableRightWrapper`],
right:[`tableRightWrapper`,`tableMainWrapper`,`tableLeftWrapper`],
}
let scrollTop = this.$refs[position[part][0]].scrollTop
//........
}
复制代码
hover
网页是不触发的,这时候就会直接return
了。
并且有时候mousewheel的区域并不必定会滚动,多是其余的元素(例如window)
目前的解决方法:每次计算scrollTop
的时候作一个记录,每次触发scrollGradient
的时候作一个判断,当前元素的scrollTop
是否等于记录的scrollTop
,是就return
。这样子就能确保每次某个部分滚动并修改其余部分的scrollTop
的时候,不会有额外的操做。
在同步滚动的过程当中,不免会由于修改元素的scrollTop
从而再次触发scroll的监听函数或者是滚动a元素的同时,快速切换滚动b元素,触发回调再次修改a元素的scrollTop
。 这可能会引起
目前尝试过的方法
passive
(没有解决)mousewheel
,修改中间滚动部分的scrollTop
,而后由中间滚动部分来同步两边的scrollTop
。(bug依然会出现,可是出现次数少了不少)最后就是在Firefox
采用监听三个部分的scroll
,用其中一个的scrollTop
来同步其余部分scrollTop
的老方法。
其余浏览器用监听两边固定部分的mouserwheel
事件,禁止两边的wheel
。控制中间滚动区域的scrollTop
,而后再给两边的scrollTop
赋值从而达到三部分同步。好处在于在几个部分滚动切换的过程当中下降了在滚动当前元素的同时再次去修改自身的scrollTop
的次数。而这种作法的确下降了错位出现的频率。
//同步滚动
const isFirefox = typeof navigator !== 'undefined' && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
const data={
currentScrollLeft:0,
currentScrollTop:0,
}
const wheel = function fixedWheel(e,target,scrollArea){
//Chrome
//....
event.preventDefault()
'中间滚动区域'.scrollTop += e.deltaY
//....
}
const scroll = function (event,el,partArr) {
//Firefox
let {scrollTop} = el
if(data.currentScrollTop===scrollTop)return
//....
this.$refs.tableMain.classList.remove('transformClass')
window.requestAnimationFrame(()=>{
//'重绘以前调用这个回调'
'若是存在的话'&&'其余部分的'.scrollTop = scrollTop//多是中间的滚动区域,也多是两边的固定区域
window.requestAnimationFrame(()=>{
this.$refs.'中间滚动区域'.classList.add('transformClass')
})
})
}
const xScroll = function('一些参数') {
if (el && el.addEventListener) {
el.addEventListener(!isFirefox?'mousewheel':'scroll', function(event) {
!isFirefox && wheel.apply(this, ['一些参数'])
isFirefox && scroll.apply(this, ['一些参数'])
})
}
}
export default {
bind(el, binding,name) {
xScroll('一些参数');
},
data
};
复制代码
要使用的时候只需
<template>
<div v-xScroll>
<table></table>
</div>
</template>
import xScroll from './同步滚动'
export default {
directives:{
xScroll
},
复制代码
目前的table
组件就是练练手,有问题的地方但愿指出。最后,厚颜无耻的求个赞,若是你以为还能够的话,哈哈。