AG-Grid在Safari 16.x中渲染不流畅问题求助
问题背景
在Safari 16.x环境下,使用 Angular 14.x + AG-Grid 28.x 实现带有自定义单元格渲染器的树形结构展示:
- 原始数据结构(简化):
| 列A | 列B | 列C | 列D |
|---|---|---|---|
| A | X | C | 1 |
| A | X | C | 2 |
| A | X | D | 1 |
| A | X | D | 2 |
| A | X | D | 3 |
| B | X | E | 1 |
- 期望将列C、D、E展示为树形合并结构,重复的列A/B内容仅在组头显示,组内子行隐藏重复列,并用分隔线区分组。
- 当前问题:网格滚动时渲染卡顿,启用
rowBuffer后DOM元素暴增,100行数据的展开/折叠操作耗时约10秒,体验极差。
优化方案
1. 自定义单元格渲染器性能优化
- 提前预处理数据:把组头判断、单元格内容隐藏逻辑移到数据加载阶段,避免在渲染器的
refresh/getGui方法中实时计算:// 示例:给数据标记组属性 this.processedData = rawData.map((row, index) => { const prevRow = this.processedData[index-1]; const isGroupHeader = !prevRow || row.colA !== prevRow.colA || row.colB !== prevRow.colB; const hideColA = !isGroupHeader && row.colA === prevRow?.colA; const hideColB = !isGroupHeader && row.colB === prevRow?.colB; return { ...row, isGroupHeader, hideColA, hideColB }; }); - 复用渲染器组件:如果使用Angular组件式渲染器,启用
reuseComponents配置减少实例创建开销;优先用轻量的ICellRendererFunc替代组件:columnDefs: [ { field: 'colA', cellRenderer: (params) => params.data.hideColA ? '' : params.value, // 组件式渲染器配置示例 // cellRenderer: 'customColRenderer', // cellRendererParams: { reuseComponents: true } } ] - 避免动态DOM操作:渲染器中尽量用模板绑定实现样式/内容控制,减少
document.createElement等直接DOM操作。
2. AG-Grid 核心配置优化
- 关闭无用特性:禁用不需要的排序、筛选、右键菜单等功能,减少事件监听和计算:
gridOptions = { enableSorting: false, enableFilter: false, suppressMenu: true, suppressRowClickSelection: true, }; - 合理设置
rowBuffer:Safari下DOM渲染性能弱于Chrome,不要盲目增大rowBuffer,建议设置为10-15(默认20),避免DOM节点过载:gridOptions = { rowBuffer: 10 }; - 固定行高+虚拟滚动优化:设置固定
rowHeight避免AG-Grid动态计算行高,开启行虚拟滚动(默认开启),列数少时可关闭列虚拟滚动:gridOptions = { rowHeight: 40, // 根据实际需求调整 suppressColumnVirtualisation: true, suppressRowVirtualisation: false, // 保持行虚拟滚动核心优化 }; - 启用Immutable Data:让AG-Grid仅在数据引用变化时更新,减少不必要重渲染:
gridOptions = { immutableData: true };
3. Safari 特定渲染优化
- 减少重绘重排:给单元格添加GPU加速提示,避免使用触发重排的样式:
.ag-cell.custom-cell { will-change: transform; transform: translateZ(0); /* 触发GPU渲染加速 */ box-sizing: border-box; /* 避免尺寸计算重排 */ } - 禁用弹性滚动:若滚动卡顿,关闭Safari原生弹性滚动:
.ag-body-viewport { overflow: auto !important; -webkit-overflow-scrolling: auto !important; }
4. 改用AG-Grid原生树形结构
放弃自定义渲染器模拟树形,将数据转换为AG-Grid支持的父子结构,利用原生树节点渲染性能更高:
// 转换后的数据结构示例 const treeData = [ { colA: 'A', colB: 'X', children: [ { colC: 'C', colD: 1 }, { colD: 2 }, { isSeparator: true }, // 分隔线标记 { colD: 1 }, { colC: 'D', colD: 2 }, { colD: 3 }, ] }, { colA: 'B', colB: 'X', children: [{ colC: 'E', colD: 1 }] } ];
配置AG-Grid树结构:
gridOptions = { treeData: true, getDataPath: (data) => ['children'], // 根据数据结构配置路径 groupDefaultExpanded: -1, // 默认展开所有节点 };
分隔线用fullWidthCellRenderer实现,减少单元格渲染压力:
// 分隔线渲染器 function separatorRenderer() { const div = document.createElement('div'); div.className = 'ag-full-width ag-separator-line'; return div; } // 列配置 columnDefs: [ { field: 'isSeparator', fullWidthCellRenderer: separatorRenderer, cellClass: 'separator-cell', }, // 其他列配置... ];
5. Angular变更检测优化
- 使用OnPush策略:自定义单元格渲染器组件设置
OnPush变更检测,减少检测频率:@Component({ selector: 'app-custom-cell', template: `<span *ngIf="!hideColA">{{ value }}</span>`, changeDetection: ChangeDetectionStrategy.OnPush }) export class CustomCellRendererComponent implements ICellRendererAngularComp { value: string; hideColA: boolean; agInit(params: ICellRendererParams): void { this.value = params.value; this.hideColA = params.data.hideColA; } refresh(params: ICellRendererParams): boolean { if (params.data.hideColA !== this.hideColA) { this.hideColA = params.data.hideColA; this.cdr.markForCheck(); return true; } return false; } }
内容的提问来源于stack exchange,提问作者Abhijit Chakrabarty
相关产品推荐
相关产品推荐

