You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 03:25:27