tableRender自适应高度

666 阅读2分钟

上篇文章讲到如何封装tableRender组件,这篇我们来讲讲对table如何实现高度自适应

我们先来看看如果数据多的情况下,如果不给table一个height或maxHeight,表格是怎样的:

2021-12-10 16.05.45.gif

我们在滑动整个页面,体验非常不好,但是我们要给table一个高度或最大高度,需要给多少呢,当窗口高度发生变化的时候,表格高度又需要重新计算,这时候又怎么拿到这个高度呢?

  • 我们首先想到的是:使用自定义指令

首先能拿到的绑定元素距离屏幕顶部的距离,当然我们还知道窗口的高度,通过: height(table) = clientHeight(窗口高度) - offsetTop(距离顶部高度) - bottomHeight(距离底部高度) 我们就可以计算出table高度了,但是bottomHeight我们获取不到,因此,这个想法行不通

  • 那么,该怎么办呢?

我们不能保证页面中table的渲染比页面中其他元素渲染的快或者慢,但是如果我们强行让table在其他元素渲染完成前不占据文档流(即table没有高度),这样拿到最顶层高度固定的容器,和流式容器的高度,根据:height(table) = flexHeight(容器高度) - flowHeight(流式容器的高度) 我们就可以得出表格高度了

  • 那我们如何让表格一开始不占据文档流(即没有高度)呢?

思路是:页面加载时设置table的display为'none',在table的所有父级都挂载完成之后,根据上述公式计算出table高度后,设置table的display为'block'

  • 那我们又如何知道table的所有父级都挂载完成呢?

我们来看一下组件实例:

7.png

当组件挂载完成后 _isMounted 会变为true,这样我们可以通过_isMounted来不断递归找到table的parant,parant,当parant为undefined时,就可以计算table的高度了

nice!让我们开始coding吧

class InitTableHeight {
  componentInstance = null // 组件实例
  el = null // 元素

  constructor (componentInstance) {
    this.componentInstance = componentInstance
    this.el = componentInstance.$refs.table.$el
    this.el.style.display = 'none'
    
    this.setHeightAfterParentMounted(this.componentInstance, () => {
      this.componentInstance.$nextTick(() => {
        this.el.style.display = 'block'
      })
    })
  }
    
 /**
   * @description: 逐级监听父组件加载完成后计算并设置表格渲染高度
   * @param {*} instance 组件实例
   * @param {*} callback 所有父组件挂载完成后的回调函数
   */
  setHeightAfterParentMounted (instance = this.componentInstance, callback) {
    if (typeof instance.$parent === 'undefined') {
      this.setTableHeight(this.getTableHeight())
      callback()
      return
    }

    if (instance.$parent._isMounted) {
      this.setHeightAfterParentMounted(instance.$parent, callback)
    } else {
      instance.$parent.$on('hook:mounted', () => {
        this.setHeightAfterParentMounted(instance.$parent, callback)
      })
    }
  }
    
  /**
   * @description: 获取表格高度
   * @return {Number} tableHeight
   */
  getTableHeight () {
    // 容器高度
    const flexHeight = this.getFlexHeiht()
    // 表格距离容器顶部高度
    const flowHeight = this.getFlowHeiht()
    // 表格高度
    let tableHeight = flexHeight - flowHeight
    const { minHeight } = this.componentInstance.fillViewCofig

    if (minHeight && tableHeight < minHeight) {
      tableHeight = minHeight
    }

    return tableHeight
  }
    
  /**
   * @description: 设置表格高度
   */
  setTableHeight (height) {
    this.componentInstance.renderHeight = height
  }

  /**
   * @description: 获取元素高度
   * @param {HTMLElement} element 元素节点
   * @return {Number} height
   */
  getElHeiht (element) {
    if (typeof element === 'string') {
      element = document.querySelector(element)
    }

    const computedStyle = getComputedStyle(element, null)
    const marginTop = parseFloat(computedStyle?.marginTop) || 0
    const marginBottom = parseFloat(computedStyle?.marginBottom) || 0
    let height = parseFloat(computedStyle?.height)
    if (typeof height !== 'number') {
      height = element?.offsetHeight ?? 0
    }
    return height + marginTop + marginBottom
  }
  /**
   * @description: 获取流的高度
   * @return {Number} flowHeiht
   */
  getFlowHeiht () {
    const { flowView = this.componentInstance.$parent.$el, flowViewHeiht } = this.componentInstance.fillViewCofig
    if (typeof flowViewHeiht === 'number') {
      return flowViewHeiht
    }

    return this.getElHeiht(flowView)
  }

  /**
   * @description: 获取容器的高度
   * @return {Number} flexHeiht
   */
  getFlexHeiht () {
    const { flexView = this.componentInstance.$root.$el, flexViewHeiht } = this.componentInstance.fillViewCofig
    if (typeof flexViewHeiht === 'number') {
      return flexViewHeiht
    }

    return this.getElHeiht(flexView)
  }
}

export default InitTableHeight

这里的fillViewConfig我们提供了以下配置:

// 自适应配置
fillViewCofig: {
  type: Object,
  default () {
    return {
      minHeight: 300, // 设置自适应后的最小高度
      flowView: undefined, // 默认: 本组件父级
      flowViewHeiht: undefined, // 优先级大于 flowView
      flexView: undefined, // 默认: 根组件
      flexViewHeight: undefined // 优先级大于 flexView
    }
  }
}
  • fillViewCofig Object 用于撑满容器高度的计算
  • minHeight Number 设置自适应后的最小高度 默认: 300
  • flowView String/HTMLElement 组件父级中高度随子级决定的流式布局的容器 默认: 本组件父级
  • flowViewHeiht Undefined/Number 指定流式容器的高度, 如果传入该值将忽略 flowView 直接使用该值进行计算 默认: undefined
  • flexView String/HTMLElement 组件父级中高度固定的容器(用于最大高度计算) 默认: 应用实例根组件
  • flexViewHeight Undefined/Number 指定定高容器高度, 如果传入该值将忽略 flexView 直接使用该值进行计算 默认: undefined

我们已经解决了初始化时table自适应高度了,如果当窗口高度变化时,我们需要重新计算table高度,只需要监听resize事件再重新执行一遍该方法即可:

import InitTableHeight from './fillView.js'
export default {
  props: {
    // 是否自适应撑满页面 设置后自定将表格 tableProps 的 maxHeight 设置为自适应最大高度, 并且 tableProps 如果传递了 height 将不再生效
    fillView: {
      type: Boolean,
      default: false
    },
    // 自适应配置
    fillViewCofig: {
      type: Object,
      default () {
        return {
          minHeight: 300, // 设置自适应后的最小高度
          flowView: undefined, // 默认: 本组件父级
          flowViewHeiht: undefined, // 优先级大于 flowView
          flexView: undefined, // 默认: 根组件
          flexViewHeight: undefined // 优先级大于 flexView
        }
      }
    }
  },
  mounted () {
    this.initCalcTableHeight()
    window.addEventListener('resize', this.initCalcTableHeight)
    this.$once('hook:beforeDestroy', () => {
      window.removeEventListener('resize', this.initCalcTableHeight)
    })
    // 用于页面缓存时更新表格高度
    this.$on('hook:activated', () => {
      this.initCalcTableHeight()
    })
  },
  methods: {
    // 重新计算表格高度
    initCalcTableHeight: _.debounce(function () {
      this.fillView && new InitTableHeight(this)
    }, 200),
  }
}

看一下效果:

2021-12-10 16.11.07.gif

相关代码:GitHub