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
組件就是練練手,有問題的地方但願指出。最後,厚顏無恥的求個贊,若是你以爲還能夠的話,哈哈。