👩‍💼产品姐一句小优化,让我给上百个列表加上一个动态实时计算高度的方法😿😿

前言:产品的日常"简单功能"

产品姐微笑着走过来:"小X啊,这个列表页面有问题,你看嗷,当我往下滑动的时候,就看不到顶部的筛选框了,又当我往上滑动的时候,嘿~你猜怎么着?又点不到底部的分页了

我:那就在table里面加上高度罗,页面滚动的时候,只在table内上下滚动

产品哥:是的,我就是这个意思。对了,是整个平台的table页面哦,加个高度应该很简单吧?给你上个新需求,给你一天时间够了吧?剩下半天随便你摸鱼啦

我:看着这个老旧的后管项目,几十个模块,各种二级、三级、甚至四级的菜单目录(这得多少个table页面????)每个都要做高度自适应?

内心OS:"什么叫就加一个Height?又要计算高度!又要考虑各种边距!还要响应式!"

一、需求分析:真如产品所说仅仅是加一个Height?为什么需要复杂的计算?

1. 规模化挑战 📊

我们的后台系统有数百个表格页面,而且由不同的开发同时进行开发,每个开发的写法都不一样,所写包裹的父盒子都不一样,内外边距也不一样,每个页面都有不同的布局结构和数据特性。手动为每个页面编写高度计算逻辑是不现实的,所以必须有一个通用解决方案。(因此需要封装一个公共的计算高度方法)

2. 动态性挑战 🔄

表格高度受多种因素影响:

  • 数据量变化:从空数据到成千上万条数据
  • 分辨率差异:从1366×768到4K各种分辨率
  • 布局调整:筛选条件变化、分页器显隐、侧边栏折叠等(页面缩小导致顶部筛选框往下挤,table高度自然变小)

3. 性能挑战 ⚡

最初版本监听所有DOM变化,结果发现:

  • 鼠标悬浮表格出现tooltip时会触发数十次高度计算
  • 表格单元格内容微调也会引起不必要的重计算
  • 甚至鼠标移入移出都会触发
  • 高频计算导致页面卡顿和性能下降

二、解决方案:智能高度计算函数

经过多次迭代优化,最终形成了这个智能高度计算方案:

PS:下面代码不适用全部项目,仅供参考,因为公共最顶层父盒子类名不同

javascript 复制代码
/**
 * 智能表格高度计算函数
 * 解决多页面、动态数据、不同分辨率下的表格高度自适应问题
 * @param {Function} fn - 高度计算完成后的回调函数
 * @param {Array} classList - 需要计算边距的容器类名数组
 */
export const getTableHeight = function (fn, classList = ['.v-app-main', '.container-fluid']) {
    let tableHeight = 0
    const bodyElement = document.body
    
    // 计算高度操作 - 核心算法
    const calculateTableHeight = () => {
        let totalPMHeight = 0
        
        // 遍历获取全部容器下内边距和下外边距高度
        classList.forEach(item => {
            const elements = document.querySelectorAll(item)
            elements.forEach(element => {
                const paddingBottom = parseInt(getComputedStyle(element).paddingBottom, 10)
                const marginBottom = parseInt(getComputedStyle(element).marginBottom, 10)
                totalPMHeight += (paddingBottom + marginBottom)
            })
        })
        
        const totalHeight = window.innerHeight
        const elTable = [...document.querySelectorAll('.el-table')].find(item => 
            item.style.display !== 'none' && item.offsetHeight
        )
        
        const paginationElement = document.querySelector('.v-pagination')
        const paginationElementHeight = paginationElement ? paginationElement.offsetHeight : 0
        
        if (elTable) {
            const rect = elTable.getBoundingClientRect()
            const topOffset = rect.top
            
            // 核心计算公式
            tableHeight = totalHeight - topOffset - paginationElementHeight - totalPMHeight
            
            console.log('高度计算详情', {
                页面总高度: totalHeight,
                表格顶部距离: topOffset,
                分页高度: paginationElementHeight,
                边距总和: totalPMHeight,
                最终表格高度: tableHeight
            })
            
            fn(tableHeight)
        }
    }
    
    // 智能监听策略:只监听真正影响布局的变化
    const mutationObserver = new MutationObserver((mutations) => {
        // 过滤出真正影响布局的DOM变化
        const layoutMutations = mutations.filter(mutation => {
            // 忽略属性和文本变化(如tooltip显示)
            if (mutation.type !== 'childList') return false
            
            // 忽略轻微的元素变化
            const hasSignificantChange = Array.from(mutation.addedNodes).some(node => {
                return node.nodeType === 1 && !node.classList.contains('el-tooltip')
            }) || Array.from(mutation.removedNodes).some(node => {
                return node.nodeType === 1 && !node.classList.contains('el-tooltip')
            })
            
            return hasSignificantChange
        })
        
        if (layoutMutations.length > 0) {
            calculateTableHeight()
        }
    })
    
    // 只监听子节点变化(忽略属性变化)
    mutationObserver.observe(bodyElement, {
        childList: true,
        subtree: true
    })
    
    // 监听窗口大小变化
    window.addEventListener('resize', calculateTableHeight)
    
    // 初始计算
    calculateTableHeight()
    
    // 组件销毁清除监听
    this.$once('hook:beforeDestroy', () => {
        mutationObserver.disconnect()
        window.removeEventListener('resize', calculateTableHeight)
    })
}

三、设计思路:如何解决三大挑战

1. 规模化解决方案 🏗️

参数化配置:通过传入容器类名数组,适配不同页面的布局结构

javascript 复制代码
// 在A页面使用
getTableHeight.call(this, (height) => {
    this.tableHeight = height
}, ['.page-a-container', '.content-wrapper'])

// 在B页面使用
getTableHeight.call(this, (height) => {
    this.tableHeight = height
}, ['.page-b-layout', '.main-content', '.table-area'])

2. 动态性应对策略 🌊

智能计算公式

text 复制代码
表格高度 = 窗口高度 - 表格顶部距离 - 分页高度 - 所有容器边距总和

关键算法解析

javascript 复制代码
// 找到真正可见的表格(解决多表格切换问题)
const elTable = [...document.querySelectorAll('.el-table')].find(item => 
    item.style.display !== 'none' && item.offsetHeight
)

// 计算所有相关容器的边距影响
classList.forEach(item => {
    const elements = document.querySelectorAll(item)
    elements.forEach(element => {
        const paddingBottom = parseInt(getComputedStyle(element).paddingBottom, 10)
        const marginBottom = parseInt(getComputedStyle(element).marginBottom, 10)
        totalPMHeight += (paddingBottom + marginBottom)
    })
})

3. 性能优化方案 ⚡

智能监听策略:只监听真正影响布局的DOM变化

javascript 复制代码
const mutationObserver = new MutationObserver((mutations) => {
    // 过滤掉不影响布局的变化(如tooltip显示)
    const layoutMutations = mutations.filter(mutation => {
        if (mutation.type !== 'childList') return false
        
        // 忽略tooltip等微小变化
        const hasSignificantChange = Array.from(mutation.addedNodes).some(node => {
            return node.nodeType === 1 && !node.classList.contains('el-tooltip')
        })
        
        return hasSignificantChange
    })
    
    if (layoutMutations.length > 0) {
        calculateTableHeight()
    }
})

四、实战应用:如何接入你的项目

基本使用方式

javascript 复制代码
import { getTableHeight } from '@/utils/tableHeight'

export default {
    mounted() {
        getTableHeight.call(this, (tableHeight) => {
            this.tableHeight = tableHeight
        }, ['.your-container-class'])
    },
    data() {
        return {
            tableHeight: 0
        }
    }
}

处理特殊场景

javascript 复制代码
// 侧边栏折叠/展开时重新计算
watch: {
    sidebarCollapsed() {
        this.$nextTick(() => {
            // 等待DOM更新后重新计算
            getTableHeight.call(this, (height) => {
                this.tableHeight = height
            }, ['.your-containers'])
        })
    }
}

// 数据加载完成后重新计算
async loadData() {
    const data = await api.getTableData()
    this.tableData = data
    this.$nextTick(() => {
        getTableHeight.call(this, (height) => {
            this.tableHeight = height
        }, ['.your-containers'])
    })
}

五、效果对比:优化前后的巨大差异

场景 初始方案 优化后方案
几百个表格页面 每个页面单独处理 ✅ 统一解决方案
数据动态变化 需要手动触发重计算 ✅ 自动响应变化
不同分辨率 媒体查询繁琐 ✅ 自动适应
性能表现 高频计算导致卡顿 ✅ 智能过滤无关变化
特殊交互 tooltip等触发重计算 ✅ 忽略不影响布局的变化

六、总结与启示

这个高度计算方案经历了从简单到智能的演进过程,主要解决了三大核心问题:

1. 规模化复用 🏗️

通过参数化设计,一个方案解决数百个页面的高度自适应需求

2. 动态适应性 🔄

智能响应数据变化、分辨率调整、布局变更等各种动态场景

3. 性能优化 ⚡

精准监听真正影响布局的DOM变化,避免不必要的重计算

关键洞察:不是所有的DOM变化都需要触发高度计算,要学会区分"影响布局的变化"和"不影响布局的变化"。

七、扩展思考

这个方案虽然是为表格高度计算设计的,但其核心思路可以应用到其他自适应场景:

  1. 弹窗内容高度计算
  2. 侧边栏自适应高度
  3. 动态表单高度适应
  4. 多标签页内容高度协调

最后的话

在老旧后台系统中实施通用解决方案确实充满挑战,但一旦找到正确的方法,就能大幅提升开发效率和用户体验。

如果你的项目也有大量表格页面需要高度自适应,不妨借鉴这个思路,根据你的具体需求进行调整优化。(本文代码仅供参考)

下次再见!🌈

相关推荐
龙在天35 分钟前
npm run dev 做了什么❓小白也能看懂
前端
hellokai1 小时前
React Native新架构源码分析
android·前端·react native
li理2 小时前
鸿蒙应用开发完全指南:深度解析UIAbility、页面与导航的生命周期
前端·harmonyos
去伪存真2 小时前
因为rolldown-vite比vite打包速度快, 所以必须把rolldown-vite在项目中用起来🤺
前端
KubeSphere2 小时前
Kubernetes v1.34 重磅发布:调度更快,安全更强,AI 资源管理全面进化
前端
wifi歪f2 小时前
🎉 Stenciljs,一个Web Components框架新体验
前端·javascript
1024小神2 小时前
如何快速copy复制一个网站,或是将网站本地静态化访问
前端
掘金一周2 小时前
DeepSeek删豆包冲上热搜,大模型世子之争演都不演了 | 掘金一周 8.28
前端·人工智能·后端
moyu842 小时前
前端存储三剑客:Cookie、LocalStorage 与 SessionStorage 全方位解析
前端