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 自动吸顶内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue实现吸顶、锚点和滚动高亮按钮效果

    因公司后台管理系统很多功能技术老旧,最近在用vue重构公司的后台管理系统,在做商品管理添加商品这一块,借鉴淘宝的添加商品的交互,需要实现一个简单的吸顶.锚点和滚动高亮按钮的效果. 需求 滚动页面到顶部,实现某元素固定到顶部效果 点击某个按钮,页面滚动到相应的位置 滚动页面,当到达某个位置时,高亮对应的相关按钮 元素吸顶实现方式 关于元素吸顶效果,通过查阅相关资料和相关测试,有三种方式(还有一种是jquery的方法,这里就不介绍了) 一.使用position:sticky 1. 什么是positi

  • Vue开发实现吸顶效果的示例代码

    因为项目需求,最近开始转到微信公众号开发,接触到了Vue框架,这个效果的实现虽说是基于Vue框架下实现的,但是同样也可以借鉴到其他地方,原理都是一样的. 进入正题,先看下效果图: 其实js做这个效果还是挺简单的,因为在css中我们可以设置一个元素的 position: fixed; ,这样它就可以固定在那里,这样不管页面怎么滚动,它的位置都不受影响,所以我们的思路就是在合适的时机把要吸顶的头部元素的position属性设置为fixed就可以了.但是这个合适的时机是什么时候呢,这就需要我们计算了,

  • vue中多路由表头吸顶实现的几种布局方式

    vue项目多路由表头吸顶实现的几种布局方式 因为项目较大,有五个界面,每个界面有四个子组件,每个子组件都有一个table表格,需求是每个界面的每个table滚动到顶端表头吸顶,所以尝试用vux做这种需求, 一.先聊js. A.首先在vux可以这样设置. 1.在state文件中设置状态. techo:{ partsFixed:false, repairFixed:false, mateFixed:false, outRepairFixed:false }//吸顶状态 2.在action中commi

  • 实现一个 Vue 吸顶锚点组件方法

    前言 近期产品小哥哥给我提了一个新需求,在一个页面的滚动区中添加一组锚点定位按钮,点击按钮将对应的元素显示在页面的可视区中.当按钮组超出页面可视区的时候将其固定在滚动区域的头部,当滚动区滚动时,高亮距离滚动区顶部最近的元素所匹配的锚点按钮. 拆分功能点 现在我们已经明确需求了,接下来我们总结一下这个需求有哪些功能点: 按钮组要有吸顶效果 点击按钮要有锚点定位功能 滚动内容区需要找到对应的按钮并高亮 吸顶组件 要做一个吸顶效果最简单的方式是将 css 的 position 属性设置为 sticky

  • vue 自定指令生成uuid滚动监听达到tab表格吸顶效果的代码

    utils/index,.js /** * 生成UUID * @param withSeparator 是否有分割符 * @returns {string} */ export function generateUUID(withSeparator = true) { let d = new Date().getTime() if (window.performance && typeof window.performance.now === 'function') { d += perf

  • vue实现某元素吸顶或固定位置显示(监听滚动事件)

    最近写了一个VUE的web app项目,需要实现某个部位吸顶的效果.即,页面往上滑动,刚好到达该部位时,该部分,固定在顶部显示. 1.监听滚动事件 利用VUE写一个在控制台打印当前的scrollTop, 首先,在mounted钩子中给window添加一个滚动滚动监听事件, mounted () { window.addEventListener('scroll', this.handleScroll) }, 然后在方法中,添加这个handleScroll方法 handleScroll () {

  • vue中el-table实现自动吸顶效果(支持fixed)

    目录 前言 实现思路 效果: 使用: 主要源码: 前言 看了很多案例,从简单的角度,position:sticky,似乎是比较理想的选择,可是当el-table设置了fixed后,这里的fixed会失效.最后还是采用了js监听滚动的思路实现. 实现思路 表格距离顶部的距离 设置表格距离顶部多少就吸顶-offsetTop1 获取滚动条滚动的距离 当滚动条滚动 offsetTop1 后,表格就自动吸顶 效果: 使用: 在el-table标签中配置:v-sticky="{ top: 0, parent

  • Vue中的table表单切换实现效果

    目录 Vue表单切换实现效果 首先给两个链接定义 一个num Vue table切换组件 Vue表单切换实现效果 点击第一个链接 出现以下数据 点击第二个链接 ,我没有写后台所以没有数据, 可以自己写方法去获取数据复制给v-model 绑定的数组 首先给两个链接定义 一个num 点击第一个按钮时 设置num等于1 , 这样在table列表处定义 v-show ="num==1 ",当等于1 时 显示第一个table 当等于num 等于 2时 等于第二个table 这样就能实现 tabl

  • 简单理解vue中el、template、replace元素

    本文实例为大家解析了vue中el.template.replace的元素,供大家参考,具体内容如下 api: http://cn.vuejs.org/api/#el el 类型: String | HTMLElement | Function 限制: 在组件定义中只能是函数. 详细: 为实例提供挂载元素.值可以是 CSS 选择符,或实际 HTML 元素,或返回 HTML 元素的函数.注意元素只用作挂载点.如果提供了模板则元素被替换,除非 replace 为 false.元素可以用 vm.$el

  • 浅谈react.js中实现tab吸顶效果的问题

    在react项目开发中有一个需求是,页面滚动到tab所在位置时,tab要固定在顶部. 实现的思路其实很简单,就是判断当滚动距离scrollTop大于tab距离页面顶部距离offsetTop时,将tab的position变为fixed. 在react中,我在state中设置一个navTop属性,切换这个属性的值为true或者false,然后tab标签使用classnames()这个方法来利用navTop的值添加类名fixed. 一开始我是这样写的: import cs from 'classnam

  • android中RecyclerView悬浮吸顶效果

    MultiType-Adapter打造悬浮吸顶效果 注:当前版本只适合配合RecyclerView快速打造一款 展示UI 悬浮吸顶效果,如 通讯录效果,由于实现机制的原因,暂时不支持触摸事件. MultiType-Adapter介绍地址:MultiType-Adapter 是一款轻量级支持多数据类型的 RecyclerView 适配器; 使用简单,完全解耦; 悬浮吸顶效果 ```groovy // root build.gradle repositories { jcenter() maven

  • vue 中 elment-ui table合并上下两行相同数据单元格

    html : <el-table :header-cell-style="{background:'#6d7f93',color:'white'}" :data="ptableDate" align="center" border v-loading="loading" :height="tableHeight" :span-method="objectOneMethod" >

  • VUE中setTimeout和setInterval自动销毁案例

    在Vue的大型单页应用中,在某个路由下,经常会出现需要延迟执行(setTimeout)或者间隔之心(setInterval)的函数,但是每次在页面destroy之前,都必须手动清理掉. 正常代码如下: beforeDestroy() { this._timer && clearTimeout(this._timer); } 但是如果一不小心,就会忘记,会造成意想不到的情况,那么有什么办法能避免这种情况吗? 当然有,那就是重新写一个setTimeout的方法(或者干脆劫持window.set

  • vue中解决chrome浏览器自动播放音频和MP3语音打包到线上的实现方法

    一.vue中解决chrome浏览器自动播放音频 需求 有新订单的时候,页面自动语音提示和弹出提示框: 问题 chrome浏览器在18年4月起,就在桌面浏览器全面禁止了音视频的自动播放功能.严格地来说,是Chrome不允许在用户对网页进行触发之前播放音频.不光是这样,在页面加载完毕的情况下,用户没有click.dbclick.touch等主动交互行为,使用js直接调用.play() 方法的话,chrome都会抛出如下错误:Uncaught (in promise) DOMException: 解决

  • react-native滑动吸顶效果的实现过程

    前言 最近公司开发方向偏向移动端,于是就被调去做RN(react-native),体验还不错,当前有个需求是首页中间吸顶的效果,虽然已经很久没写样式了,不过这种常见样式应该是so-easy,没成想翻车了,网上搜索换了几个方案都不行,最后去github上复制封装好的库来实现,现在把翻车过程记录下来. 需求效果 翻车过程 第一种方案 失败 一开始的思路是这样的,大众思路,我们需要监听页面的滚动状态,当页面滚动到要吸顶元素所处的位置的时候,我们设置它为固定定位,不过很遗憾,RN对于position属性

随机推荐