Grid+Subgrid可折叠表格的布局与视觉动画不同步问题排查求助
Grid+Subgrid可折叠表格的布局与视觉动画不同步问题排查求助
我正在尝试用Grid和Subgrid实现一个可折叠表格,需求是折叠后只显示3行,显示的行取决于用户选中的行。动画部分我用translateY让行块靠拢,同时通过计算显示行的数量来设置表格的高度实现整体折叠。选择Subgrid而非Flex是因为我没法给列设置固定宽度限制,需要右侧列自适应内容,左侧列自动拉伸。
但现在遇到了麻烦:折叠动画过程中出现了明显的滞后不同步,中间状态下视觉效果错位,行的移动和表格高度变化没对齐。
下面是我的完整实现代码,包含JS逻辑和CSS样式:
JavaScript 逻辑
let isOpen = false; let state = { items: [], visibleRows: 3, isMultiSelect: false, }; // 初始化状态 function initializeState() { const rows = document.querySelectorAll('.table-row'); state.items = Array.from(rows).map((row, index) => ({ id: index, selected: row.querySelector('input').checked, transformMultiplier: 0, element: row, })); calculateTransforms(); } // 计算行的位移乘数(从React组件移植过来的逻辑) function calculateTransforms() { const selectedRows = state.items.reduce( (count, item) => count + (item.selected ? 1 : 0), 0, ); let gracePortion = Math.max(0, 3 - selectedRows); let distance = 0; const distances = []; const firstPass = state.items.map((item, index) => { let transformMultiplier = null; if (item.selected) { transformMultiplier = distance; distances.push(distance); } else if (gracePortion > 0) { transformMultiplier = distance; distances.push(distance); gracePortion--; } else if (state.items.length === index + 1) { distance++; transformMultiplier = distance; distances.push(distance); } else { distance++; } return { ...item, transformMultiplier: transformMultiplier }; }); let distancePosition = 0; const items = firstPass.map((item) => { let transformMultiplier = item.transformMultiplier; if (transformMultiplier === null) { transformMultiplier = distances[distancePosition]; } else { distancePosition++; } return { ...item, transformMultiplier: transformMultiplier }; }); state.items = items; state.visibleRows = Math.max(3, selectedRows); updateTransforms(); } // 更新行的位移和表格高度 function updateTransforms() { const table = document.getElementById('table'); const totalRows = isOpen ? state.items.length : state.visibleRows; // 更新CSS自定义属性控制表格高度 table.style.setProperty('--number-of-rows', totalRows); state.items.forEach((item, index) => { const translateY = isOpen ? 0 : -item.transformMultiplier * 100; item.element.style.transform = `translateY(${translateY}%)`; item.element.style.zIndex = state.items.length - index; }); } // 切换展开/折叠状态 function toggleRows() { isOpen = !isOpen; const button = document.querySelector('.footer-button'); button.innerHTML = isOpen ? 'Show less <span class="chevron open" id="chevron"></span>' : 'Show more <span class="chevron" id="chevron"></span>'; calculateTransforms(); } // 处理单选框选择变化 function handleSelectionChange() { const rows = document.querySelectorAll('.table-row'); rows.forEach((row, index) => { const input = row.querySelector('input'); state.items[index].selected = input.checked; }); calculateTransforms(); } // 页面加载完成后初始化 document.addEventListener('DOMContentLoaded', function () { initializeState(); // 给单选框绑定事件 const inputs = document.querySelectorAll('input[type="radio"]'); inputs.forEach((input) => { input.addEventListener('change', handleSelectionChange); }); });
CSS 样式
:root { --duration: 500ms; } .table-container { display: flex; flex-direction: column; width: 100%; max-width: 600px; margin: 20px auto; } .table { --number-of-rows: 3; position: relative; display: grid; grid-template-columns: auto max-content; overflow: hidden; height: calc(var(--number-of-rows) * 51px + 42px); transition: height var(--duration) ease; } .table-header { display: grid; grid-template-columns: subgrid; grid-column: span 2; background-color: grey; height: 40px; align-items: center; color: white; font-weight: bold; border: solid 1px black; z-index: 9999; } .table-row { display: grid; grid-template-columns: subgrid; grid-column: span 2; height: 50px; align-items: center; border-inline: solid 1px red; border-bottom: solid 1px red; transition: transform var(--duration) ease; } .table-row:nth-child(even) { background-color: #f8f8f8; } .table-row:nth-child(odd) { background-color: #e8e8e8; } .table-row input[type="radio"] { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; } .table-row:has(:checked) { outline: 2px solid green; outline-offset: -2px; } .footer-button { width: 100%; padding: 10px; background-color: #007bff; color: white; border: none; cursor: pointer; text-align: center; transition: background-color 0.2s ease; } .footer-button:hover { background-color: #0056b3; } .item { padding: 10px; } .chevron { display: inline-block; width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 5px solid white; margin-left: 5px; } .chevron.open { border-top: none; border-bottom: 5px solid white; }
我排查了很久,怀疑是行的translateY动画和表格高度动画的时序或者计算逻辑有问题,但没找到根因。有没有大佬能帮忙分析下,为什么动画过程中会出现滞后不同步,以及怎么修复这个问题?
内容来源于stack exchange
相关产品推荐
相关产品推荐

