目录
- 前言
- 实现思路
- 效果:
- 使用:
- 主要源码:
前言
看了很多案例,从简单的角度,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 自动吸顶内容请搜索NICE源码以前的文章或继续浏览下面的相关文章希望大家以后多多支持NICE源码!