vue中el-table實現自動吸頂效果(支持fixed)
前言
看瞭很多案例,從簡單的角度,position:sticky,似乎是比較理想的選擇,可是當el-table設置瞭fixed後,這裡的fixed會失效。最後還是采用瞭js監聽滾動的思路實現。
實現思路
- 表格距離頂部的距離
- 設置表格距離頂部多少就吸頂—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>
說明
參數名字 | 類型 | 說明 |
---|---|---|
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 }) }, // 具體判斷是否固定頭的主函數 fixHead1: sticky_throttle((_this, { scrollDom, el, uid, binding }) => { const top = binding.value.top /** * myTop 當前元素距離滾動父容器的高度, * fixtop 當前元素需要設置的絕對定位的高度 * parentHeight 滾動父容器的高度 */ // 表頭DOM節點 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 // 給表格設置寬度。這裡默認一個頁面中的多個表格寬度是一樣的。所以直接遍歷賦值,也可以根據自己需求,單獨設置 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) // 判斷頂部是否已吸頂的一個過程 }), 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 // 取第一個 } } } }, // 監聽父級的某些變量(父級一定要有才能被監聽到) watched ({ el, binding, vnode, uid }) { // 監聽左側導航欄是否折疊 vnode.context.$watch('isNavFold', (val) => { vnode.context.$nextTick(() => { setTimeout(() => { // debugger this.setHeadWidth({ el, uid, binding, eventType: 'resize' }) }, 200) }) }) } } /** * 節流函數: 指定時間間隔內隻會執行一次任務 * @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) } } /** * 防抖: 指定時間間隔內隻會執行一次任務,並且該時間段內再觸發,都會重新計算時間。(函數防抖的非立即執行版) * 在頻繁觸發某些事件,導致大量的計算或者非常消耗資源的操作的時候,防抖可以強制在一段連續的時間內隻執行一次 * */ 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。作為存放各種監聽事件的key const uid = vnode.componentInstance._uid // 獲取當前滾動的容器是什麼。如果是document滾動。則可默認不傳入parent參數 const scrollDom = document.querySelector(binding.value.parent) || document.body // TODO:得考慮沒有 binding.value.parent 的情況,重新登錄直接進到內頁會出現 if (!tableStickyObj[uid]) { tableStickyObj[uid] = { uid, fixFunObj: {}, // 用於存放滾動容器的監聽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 }) // 監聽父級的某些變量 // 當window resize時 重新計算設置表頭寬度,並將監聽函數存入 監聽函數對象中,方便移除監聽事件 window.addEventListener('resize', (tableStickyObj[uid].setWidthFunObj = () => { __STICKY_TABLE.setHeadWidth({ el, uid, binding, eventType: 'resize' }) // 首先設置表頭寬度 }) ) // 給滾動容器加scroll監聽事件。並將監聽函數存入 監聽函數對象中,方便移除監聽事件 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' }) }, // 節點取消綁定時 移除各項監聽事件。 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實現自動吸頂效果(支持fixed)的文章就介紹到這瞭,更多相關el-table 自動吸頂內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- el-table表頭根據內容自適應完美解決表頭錯位和固定列錯位
- element 表格多級表頭子列固定的實現
- vue3 自定義指令詳情
- Vue組件設計-Sticky佈局效果示例
- vue自定義封裝指令以及實際使用