窗口resize时如何正确重算嵌套手风琴下拉块的高度
问题场景
现有带嵌套层级的下拉手风琴菜单,为实现平滑展开动画效果,页面加载时会计算每个内容块的高度,将高度值写入块的dataset自定义属性后把块高度设为0实现隐藏;窗口触发resize事件时,尝试复用相同逻辑重新计算高度,对应实现代码如下:
function activeHeight() { let allAccordion = document.querySelectorAll('.accordion'); allAccordion.forEach((item) => { let accordionParent = item.querySelectorAll('.accordion-parent'); let accordionItem = item.querySelectorAll('.accordion-item'); accordionItem.forEach((item) => { item.nextElementSibling.style.height = 'auto'; item.nextElementSibling.dataset.height = item.nextElementSibling.offsetHeight; item.nextElementSibling.style.height = '0'; }); accordionItem.forEach((item) => { if (item.classList.contains('active')) { let element = item.closest('.accordion-parent'); if (element?.querySelectorAll('.accordion-item.active')) { const allElement = element.querySelectorAll('.accordion-item.active'); allElement.forEach((item) => { let innerElement = item.nextElementSibling.querySelectorAll('.accordion-item.active'); let countInnerElement = +item.nextElementSibling.dataset.height; innerElement.forEach((element) => { countInnerElement += +element.nextElementSibling.dataset.height; }); item.nextElementSibling.style.height = countInnerElement + 'px'; }); } } }); }); }
代码运行后出现高度计算结果不一致、数值异常的问题,无法得到正确的块高度,相关异常效果参考截图:


问题根因
- 测量逻辑时序错误:第一轮遍历逐一把块设为
auto、读高度、立刻设为0,这个过程中嵌套的子块还处于高度0的隐藏状态,读到的父块高度没有包含子项内容,存入dataset的基础高度值本身就是错误的。 - 高度累加重复计算:遍历激活项累加子项高度时没有做层级限制,会把深层嵌套的子项高度重复加到每一层父块上,最终算出的高度远大于实际值。
- 缺少强制重排步骤:修改DOM样式后没有触发浏览器重排,部分场景下读到的
offsetHeight是浏览器缓存的旧值,导致计算结果随机不一致。 - 存在冗余代码:声明了未使用的变量
accordionParent,不影响功能但会增加代码维护成本。
修复方案
调整计算逻辑:先把所有内容块重置为自动高度,触发浏览器重排后,先记录每个块自身不含嵌套子项的原生高度,再按嵌套深度从深到浅处理激活项,只累加直接子激活项的高度,避免重复计算。修复后完整代码如下:
function activeHeight() { const allAccordion = document.querySelectorAll('.accordion'); allAccordion.forEach(accordion => { // 选中所有手风琴内容块(即.accordion-item的相邻下一个节点) const allContentBlocks = accordion.querySelectorAll('.accordion-item + *'); // 第一步:重置所有块状态准备测量 allContentBlocks.forEach(block => { block.style.height = 'auto'; block.style.overflow = 'hidden'; }); // 触发强制重排,确保auto高度真实渲染 void accordion.offsetHeight; // 第二步:存储每个块自身的基础高度,重置为隐藏状态 allContentBlocks.forEach(block => { block.dataset.selfHeight = block.offsetHeight; block.style.height = '0'; }); // 第三步:从最内层激活项开始向外逐层计算总高度 const allActiveItems = Array.from(accordion.querySelectorAll('.accordion-item.active')); // 按嵌套深度排序,嵌套层级越深的项越先处理 allActiveItems.sort((a, b) => b.querySelectorAll('.accordion-item').length - a.querySelectorAll('.accordion-item').length); allActiveItems.forEach(activeItem => { const contentBlock = activeItem.nextElementSibling; let totalHeight = Number(contentBlock.dataset.selfHeight); // 只累加直接子级的激活块高度,不跨层重复统计 const directChildActiveBlocks = contentBlock.querySelectorAll(':scope > .accordion-parent > .accordion-item.active + *'); directChildActiveBlocks.forEach(childBlock => { totalHeight += Number(childBlock.offsetHeight); }); contentBlock.style.height = `${totalHeight}px`; }); }); }
配套优化
- 给所有手风琴内容块加基础样式,保证动画效果正常:
.accordion-item + * { overflow: hidden; transition: height 0.25s ease; }
- 给resize事件加防抖,避免窗口拖动时频繁触发计算导致卡顿:
let resizeTimer = null; window.addEventListener('resize', () => { clearTimeout(resizeTimer); resizeTimer = setTimeout(activeHeight, 120); });
内容的提问来源于stack exchange,提问作者narem
相关产品推荐
相关产品推荐

