Angular Material Table v11.1.0:展开行滚出后取消主行粘性
解决方案
1. 模板与样式调整
首先给表格滚动容器添加滚动监听,同时给主行绑定动态类来控制粘性状态:
- 在表格的滚动容器(如
<div class="table-container">)上添加(scroll)="onTableScroll($event)" - 给主行元素添加
[class.sticky-row]="shouldSticky(row.id)",通过行ID关联状态
修改粘性样式,仅在子行可视时生效:
.table-container { max-height: 400px; overflow-y: auto; } .sticky-row { position: sticky; top: 0; z-index: 1; background: white; } .mat-row:not(.sticky-row) { position: relative; }
2. 组件核心逻辑实现
在组件中维护展开行状态、缓存DOM元素,并通过滚动事件检测子行可视性:
// 记录展开的行ID与状态 expandedRows: { [key: number]: boolean } = {}; // 缓存主行和子行的DOM元素 rowRefs = new Map<number, HTMLElement>(); subRowRefs = new Map<number, HTMLElement>(); // 切换行展开状态时缓存DOM引用 toggleExpand(row: any, rowEl: HTMLElement) { this.expandedRows[row.id] = !this.expandedRows[row.id]; this.rowRefs.set(row.id, rowEl); // 子行是主行的下一个兄弟元素 const subRowEl = rowEl.nextElementSibling as HTMLElement; this.subRowRefs.set(row.id, subRowEl); } // 滚动事件处理:检查展开子行是否完全滚出视图 onTableScroll(event: Event) { const container = event.target as HTMLElement; const containerTop = container.scrollTop; const containerBottom = containerTop + container.clientHeight; Object.keys(this.expandedRows).forEach(idStr => { const rowId = parseInt(idStr); if (!this.expandedRows[rowId]) return; const subRowEl = this.subRowRefs.get(rowId); if (!subRowEl) return; const subRowRect = subRowEl.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); // 计算子行相对于滚动容器的位置 const subRowBottom = subRowRect.bottom - containerRect.top + container.scrollTop; // 子行完全滚出容器顶部时,取消主行粘性 const isOutOfView = subRowBottom <= containerTop; const rowEl = this.rowRefs.get(rowId); if (rowEl) { rowEl.classList.toggle('sticky-row', !isOutOfView); } }); } // 判断该行是否需要保持粘性 shouldSticky(rowId: number): boolean { if (!this.expandedRows[rowId]) return false; const subRowEl = this.subRowRefs.get(rowId); if (!subRowEl) return false; const container = document.querySelector('.table-container') as HTMLElement; const containerTop = container.scrollTop; const subRowRect = subRowEl.getBoundingClientRect(); const containerRect = container.getBoundingClientRect(); const subRowBottom = subRowRect.bottom - containerRect.top + container.scrollTop; return subRowBottom > containerTop; }
3. 模板绑定细节
在模板中给主行绑定点击事件和DOM引用:
<div class="table-container" (scroll)="onTableScroll($event)"> <table mat-table [dataSource]="dataSource" multiTemplateDataRows> <!-- 主行定义 --> <tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="toggleExpand(row, $event.target.closest('tr'))" [class.sticky-row]="shouldSticky(row.id)"> </tr> <!-- 子行定义 --> <tr mat-row *matRowDef="let row; columns: ['expandedDetail'];" class="detail-row"></tr> </table> </div>
关键注意点
- 用
getBoundingClientRect()计算元素位置时,要结合滚动容器的偏移量,避免视口坐标偏差 - 缓存DOM元素减少重复查询,提升滚动时的性能
- 依赖Angular变更检测确保样式实时更新,若滚动频繁可考虑使用
ChangeDetectorRef手动触发
内容的提问来源于stack exchange,提问作者Ramkumar
相关产品推荐
相关产品推荐

