上篇文章讲到如何封装tableRender组件,这篇我们来讲讲对table如何实现高度自适应
我们先来看看如果数据多的情况下,如果不给table一个height或maxHeight,表格是怎样的:
我们在滑动整个页面,体验非常不好,但是我们要给table一个高度或最大高度,需要给多少呢,当窗口高度发生变化的时候,表格高度又需要重新计算,这时候又怎么拿到这个高度呢?
- 我们首先想到的是:使用自定义指令
首先能拿到的绑定元素距离屏幕顶部的距离,当然我们还知道窗口的高度,通过: height(table) = clientHeight(窗口高度) - offsetTop(距离顶部高度) - bottomHeight(距离底部高度) 我们就可以计算出table高度了,但是bottomHeight我们获取不到,因此,这个想法行不通
- 那么,该怎么办呢?
我们不能保证页面中table的渲染比页面中其他元素渲染的快或者慢,但是如果我们强行让table在其他元素渲染完成前不占据文档流(即table没有高度),这样拿到最顶层高度固定的容器,和流式容器的高度,根据:height(table) = flexHeight(容器高度) - flowHeight(流式容器的高度) 我们就可以得出表格高度了
- 那我们如何让表格一开始不占据文档流(即没有高度)呢?
思路是:页面加载时设置table的display为'none',在table的所有父级都挂载完成之后,根据上述公式计算出table高度后,设置table的display为'block'
- 那我们又如何知道table的所有父级都挂载完成呢?
我们来看一下组件实例:
当组件挂载完成后 _isMounted 会变为true,这样我们可以通过_isMounted来不断递归找到table的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),
}
}
看一下效果:
相关代码:GitHub