vue中el-table實現(xiàn)自動吸頂效果(支持fixed)
前言
看了很多案例,從簡單的角度,position:sticky,似乎是比較理想的選擇,可是當el-table設置了fixed后,這里的fixed會失效。最后還是采用了js監(jiān)聽滾動的思路實現(xiàn)。
實現(xiàn)思路
- 表格距離頂部的距離
- 設置表格距離頂部多少就吸頂—offsetTop1
- 獲取滾動條滾動的距離
- 當滾動條滾動 offsetTop1 后,表格就自動吸頂
效果:

使用:
在el-table標簽中配置:v-sticky="{ top: 0, parent:'#appMainDom'}",
<el-table
:data="tableData" style="margin:10px 0;width: 100%;"
bordermax-height="800" class="sticky-head" v-sticky="{ top: 0, parent:'#appMainDom' }" >
...
</el-table>
說明
| 參數(shù)名字 | 類型 | 說明 |
|---|---|---|
| top | Number | 滾動條距離頂部多少像素,自動吸頂 |
| parent | String | 滾動的dom元素,內部使用querySelector獲取該元素 |
gitee案例源碼:
https://gitee.com/kaiking_g/test-element-by-vue.git
主要源碼:
/**
* 思路:
* 表格距離頂部的距離
* 設置表格距離頂部多少就吸頂---offsetTop1
* 獲取滾動條滾動的距離
* 當滾動條滾動 offsetTop1 后,表格就自動吸頂
*/
import Vue from 'vue'
const tableStickyObj = {}
const __STICKY_TABLE = {
// 給固定頭設置樣式
doFix (dom, top, data) {
const { uid, domType, isExist } = data
const uObj = tableStickyObj[uid]
const curObj = uObj[domType]
const headerRect = tableStickyObj[uid].headerRect
if (!isExist) {
dom.style.position = 'fixed'
dom.style.zIndex = '2001'
dom.style.top = top + 'px'
}
uObj.tableWrapDom.style.marginTop = headerRect.height + 'px'
if (domType === 'fixed') {
dom.style.left = curObj.left + 'px'
} else if (domType === 'fixedRight') {
dom.style.left = curObj.left + 1 + 'px'
}
},
// 給固定頭取消樣式
removeFix (dom, data) {
const { uid, domType } = data
// dom.parentNode.style.paddingTop = 0
const uObj = tableStickyObj[uid]
const curObj = uObj[domType]
dom.style.position = 'static'
dom.style.top = '0'
dom.style.zIndex = '0'
uObj.tableWrapDom.style.marginTop = '0'
if (domType === 'fixed') {
curObj.dom.style.top = '0'
} else if (domType === 'fixedRight') {
curObj.dom.style.top = '0'
}
},
// 給固定頭添加class
addClass (dom, fixtop, data) {
fixtop = fixtop || 0
const isExist = dom.classList.contains('fixed')
data.isExist = !!isExist
if (!isExist) { // 若有,就不再添加
dom.classList.add('fixed')
}
this.doFix(dom, fixtop, data)
},
// 給固定頭移除class
removeClass (dom, data) {
if (dom.classList.contains('fixed')) {
dom.classList.remove('fixed')
this.removeFix(dom, data)
}
},
/**
* 計算某元素距離相對父元素的top距離
* @param {Nodes} e 某元素
* @param {String} domId 父元素id
* @param {Boolean} isParent 是否父元素
* @returns {Number}
*/
getPosY (el, domId) {
let offset = 0
const pDom = el.offsetParent
if (pDom != null && '#' + el.id !== domId) {
offset = el.offsetTop
offset += this.getPosY(pDom, domId)
}
return offset
},
// 獲取元素的橫坐標(相對于窗口)
getPosX (e) {
var offset = e.offsetLeft
if (e.offsetParent != null) offset += this.getPosX(e.offsetParent)
return offset
},
fixHead (scrollDom, el, uid, binding) {
this.fixHead1(this, { scrollDom, el, uid, binding })
},
// 具體判斷是否固定頭的主函數(shù)
fixHead1: sticky_throttle((_this, { scrollDom, el, uid, binding }) => {
const top = binding.value.top
/**
* myTop 當前元素距離滾動父容器的高度,
* fixtop 當前元素需要設置的絕對定位的高度
* parentHeight 滾動父容器的高度
*/
// 表頭DOM節(jié)點
const headerWrapDom = el.children[1] // el-table__header-wrapper
const headerTop = tableStickyObj[uid].headerRect.top
const scrollTop = scrollDom.scrollTop
const fixedHeadDom = tableStickyObj[uid].fixed.headerDom
const fixedHeadRightDom = tableStickyObj[uid].fixedRight.headerDom
if (scrollTop >= headerTop) {
const fixtop = top + scrollDom.getBoundingClientRect().top
// 如果表頭滾動到 父容器頂部了。fixed定位
_this.addClass(headerWrapDom, fixtop, { domType: 'mainBody', uid })
fixedHeadDom && _this.addClass(fixedHeadDom, fixtop, { domType: 'fixed', uid })
fixedHeadRightDom && _this.addClass(fixedHeadRightDom, fixtop, { domType: 'fixedRight', uid })
} else {
// 如果表格向上滾動 又滾動到父容器里。取消fixed定位
_this.removeClass(headerWrapDom, { domType: 'mainBody', uid })
fixedHeadDom && _this.removeClass(fixedHeadDom, { domType: 'fixed', uid })
fixedHeadRightDom && _this.removeClass(fixedHeadRightDom, { domType: 'fixedRight', uid })
}
}, 100, { eventType: 'fixHead111' }),
//
setHeadWidth (data) {
this.setHeadWidth1(this, data)
},
// 設置頭部固定時表頭外容器的寬度寫死為表格body的寬度
setHeadWidth1: sticky_debounce((_this, data) => {
const { el, uid, binding, eventType } = data
const { scrollDom } = tableStickyObj[uid]
const headerWrapDom = el.children[1] // el-table__header-wrapper
const headerH = headerWrapDom.offsetHeight
const distTop = _this.getPosY(headerWrapDom, binding.value.parent)
const scrollDistTop = _this.getPosY(scrollDom) // 滾動條距離頂部的距離
tableStickyObj[uid].headerRect.top = distTop + headerH - scrollDistTop / 3 // 表頭距離頂部的距離 - 表頭自身高度 - 滾動條距離頂部的距離
tableStickyObj[uid].headerRect.height = headerH
// tableStickyObj[uid].headerRect.width = tableW
// debugger
// fixed left/right header
// 確保每次刷新,只獲取一次
// tableStickyObj[uid].fixed.dom = ''
_this.initFixedWrap({ el, uid, eventType, key: 'fixed', className: 'el-table__fixed', className1: 'el-table__fixed-header-wrapper' })
_this.initFixedWrap({ el, uid, eventType, key: 'fixedRight', className: 'el-table__fixed-right', className1: 'el-table__fixed-header-wrapper' })
// debugger
// 獲取到當前表格個表格body的寬度
const bodyWrapperDom = el.getElementsByClassName('el-table__body-wrapper')[0]
const width = getComputedStyle(bodyWrapperDom).width
// 給表格設置寬度。這里默認一個頁面中的多個表格寬度是一樣的。所以直接遍歷賦值,也可以根據(jù)自己需求,單獨設置
const tableParent = el.getElementsByClassName('el-table__header-wrapper')
for (let i = 0; i < tableParent.length; i++) {
tableParent[i].style.width = width
}
// debugger
_this.fixHead(scrollDom, el, uid, binding) // 判斷頂部是否已吸頂?shù)囊粋€過程
}),
initFixedWrap (data) {
const { key, el, eventType, className, className1, uid } = data
// 確保每次刷新,只獲取一次
if (eventType === 'resize' || !tableStickyObj[uid][key].dom) {
const tableFixedDom = el.getElementsByClassName(className)
if (tableFixedDom.length) {
const fixedDom = tableFixedDom[0]
const arr = fixedDom.getElementsByClassName(className1) //
const headW = getComputedStyle(fixedDom).width
tableStickyObj[uid][key].dom = fixedDom
if (arr.length) {
const distLeft = this.getPosX(fixedDom) // 距離窗口左側的距離
const headDom = arr[0]
headDom.style.width = headW
tableStickyObj[uid][key].left = distLeft // 距離窗口左邊像素
if (key === 'fixedRight') { // right-fixed 的特別之處
headDom.classList.add('scroll-bar-h0')
headDom.style.overflow = 'auto'
headDom.scrollLeft = headDom.scrollWidth
headDom.style.overflow = 'hidden' // 設置了滾動到最后,設置不可滾動
} else {
headDom.style.overflow = 'hidden'
}
tableStickyObj[uid][key].headerDom = headDom // 取第一個
}
}
}
},
// 監(jiān)聽父級的某些變量(父級一定要有才能被監(jiān)聽到)
watched ({ el, binding, vnode, uid }) {
// 監(jiān)聽左側導航欄是否折疊
vnode.context.$watch('isNavFold', (val) => {
vnode.context.$nextTick(() => {
setTimeout(() => {
// debugger
this.setHeadWidth({ el, uid, binding, eventType: 'resize' })
}, 200)
})
})
}
}
/**
* 節(jié)流函數(shù): 指定時間間隔內只會執(zhí)行一次任務
* @param {function} fn
* @param {Number} interval
*/
function sticky_throttle (fn, interval = 300) {
let canRun = true
return function () {
if (!canRun) return
canRun = false
setTimeout(() => {
fn.apply(this, arguments)
canRun = true
}, interval)
}
}
/**
* 防抖: 指定時間間隔內只會執(zhí)行一次任務,并且該時間段內再觸發(fā),都會重新計算時間。(函數(shù)防抖的非立即執(zhí)行版)
* 在頻繁觸發(fā)某些事件,導致大量的計算或者非常消耗資源的操作的時候,防抖可以強制在一段連續(xù)的時間內只執(zhí)行一次
* */
function sticky_debounce (fn, delay, config) {
const _delay = delay || 200
config = config || {}
// const _this = this // 該this指向common.js
return function () {
const th = this // 該this指向實例
const args = arguments
// debounceNum++
// let str = `, label: ${th && th.listItem && th.listItem.label}`
if (fn.timer) {
clearTimeout(fn.timer)
fn.timer = null
} else {
// fn.debounceNum = debounceNum
}
fn.timer = setTimeout(function () {
// str = `, label: ${th && th.listItem && th.listItem.label}`
fn.timer = null
fn.apply(th, args)
}, _delay)
}
}
// 全局注冊 自定義事件
Vue.directive('sticky', {
// 當被綁定的元素插入到 DOM 中時……
inserted (el, binding, vnode) {
// 獲取當前vueComponent的ID。作為存放各種監(jiān)聽事件的key
const uid = vnode.componentInstance._uid
// 獲取當前滾動的容器是什么。如果是document滾動。則可默認不傳入parent參數(shù)
const scrollDom = document.querySelector(binding.value.parent) || document.body // TODO:得考慮沒有 binding.value.parent 的情況,重新登錄直接進到內頁會出現(xiàn)
if (!tableStickyObj[uid]) {
tableStickyObj[uid] = {
uid,
fixFunObj: {}, // 用于存放滾動容器的監(jiān)聽scroll事件
setWidthFunObj: {}, // 用于存放頁面resize后重新計算head寬度事件
autoMoveFunObj: {}, // 用戶存放如果是DOM元素內局部滾動時,document滾動時,fix布局的表頭也需要跟著document一起向上滾動
scrollDomRect: {},
headerRect: { top: 0, left: 0 },
fixed: {}, // 表格左浮動
fixedRight: {}, // 表格右浮動
// binding,
// el,
tableWrapDom: el.getElementsByClassName('el-table__body-wrapper')[0],
scrollDom
}
}
__STICKY_TABLE.watched({ el, binding, vnode, uid }) // 監(jiān)聽父級的某些變量
// 當window resize時 重新計算設置表頭寬度,并將監(jiān)聽函數(shù)存入 監(jiān)聽函數(shù)對象中,方便移除監(jiān)聽事件
window.addEventListener('resize', (tableStickyObj[uid].setWidthFunObj = () => {
__STICKY_TABLE.setHeadWidth({ el, uid, binding, eventType: 'resize' }) // 首先設置表頭寬度
})
)
// 給滾動容器加scroll監(jiān)聽事件。并將監(jiān)聽函數(shù)存入 監(jiān)聽函數(shù)對象中,方便移除監(jiān)聽事件
scrollDom.addEventListener('scroll', (tableStickyObj[uid].fixFunObj = (e) => {
__STICKY_TABLE.fixHead(scrollDom, el, uid, binding)
}))
},
// component 更新后。重新計算表頭寬度
componentUpdated (el, binding, vnode) {
const uid = vnode.componentInstance._uid
__STICKY_TABLE.setHeadWidth({ el, uid, binding, eventType: 'componentUpdated' })
},
// 節(jié)點取消綁定時 移除各項監(jiān)聽事件。
unbind (el, binding, vnode) {
const uid = vnode.componentInstance._uid
window.removeEventListener('resize', tableStickyObj[uid].setWidthFunObj)
const scrollDom = document.querySelector(binding.value.parent) || document
scrollDom.removeEventListener('scroll', tableStickyObj[uid].fixFunObj)
if (binding.value.parent) {
document.removeEventListener('scroll', tableStickyObj[uid].autoMoveFunObj)
}
}
})
到此這篇關于vue中el-table實現(xiàn)自動吸頂效果(支持fixed)的文章就介紹到這了,更多相關el-table 自動吸頂內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
解決vue中修改export default中腳本報一大堆錯的問題
今天小編就為大家分享一篇解決vue中修改export default中腳本報一大堆錯的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-08-08
非Vuex實現(xiàn)的登錄狀態(tài)判斷封裝實例代碼
這篇文章主要給大家介紹了關于非Vuex實現(xiàn)的登錄狀態(tài)判斷封裝的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2022-02-02

