AG-Grid水平滚动时如何稳定一致地自动调整虚拟列宽
AG-Grid 虚拟化表格动态列宽自动调整优化方案
问题根因
现有实现效果不稳定的核心原因有三点:
- 监听的
processRowPostCreate、onVirtualColumnsChanged事件触发时机早于虚拟列DOM的最终渲染,此时调用autoSizeColumns无法获取单元格真实内容宽度,计算结果存在偏差 - 水平滚动时
onVirtualColumnsChanged触发频率和滚动帧率一致,同步执行列宽计算会频繁触发表格重排,和渲染线程抢占资源,导致滚动卡顿、计算逻辑跟不上滚动速度 - 首屏初始化阶段没有在数据首次渲染完成后触发计算,导致首次加载时列宽不会自动调整
优化方案
- 移除
processRowPostCreate中的自动调整逻辑,该钩子触发时新挂载的虚拟列单元格还未完成内容绘制,计算结果无参考价值 - 所有列宽计算逻辑通过
requestAnimationFrame推迟到浏览器下一帧渲染周期执行,确保DOM已经完成绘制,能拿到准确的内容宽度 - 给滚动触发的列宽调整逻辑加防抖,快速滚动时只在滚动停止100~150ms后执行一次计算,避免不必要的重排消耗
- 监听
onFirstDataRendered事件,首屏数据渲染完成后主动触发一次列宽计算,解决初始化不生效的问题 - 增加已计算列缓存,已经调整过宽度的列重复滚动到时不再重复计算,减少性能消耗
优化后参考代码
import { AgGridReact } from 'ag-grid-react'; import { GridOptions, VirtualColumnsChangedEvent } from 'ag-grid-community'; import { useMemo, useRef, useCallback } from 'react'; // 通用防抖工具 const debounce = <T extends (...args: any[]) => void>(fn: T, delay: number) => { let timer: NodeJS.Timeout | null = null; return (...args: Parameters<T>) => { if (timer) clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); } } const App = () => { const gridRef = useRef<AgGridReact>(null); // 缓存已完成宽度计算的列ID,避免重复计算 const sizedColumnsCache = useRef<Set<string>>(new Set()); // 核心列宽调整逻辑 const autoSizeVisibleColumns = useCallback(() => { const columnApi = gridRef.current?.columnApi; if (!columnApi) return; // 等当前帧DOM渲染完成后再执行计算 requestAnimationFrame(() => { const virtualColumns = columnApi.getAllDisplayedVirtualColumns(); const pendingResizeCols = virtualColumns.filter( col => !sizedColumnsCache.current.has(col.getColId()) ); if (pendingResizeCols.length === 0) return; columnApi.autoSizeColumns(pendingResizeCols, false); pendingResizeCols.forEach(col => sizedColumnsCache.current.add(col.getColId())); }) }, []) // 防抖包装滚动触发的调整逻辑,快速滚动时不重复触发 const debouncedResize = useMemo( () => debounce(autoSizeVisibleColumns, 120), [autoSizeVisibleColumns] ); const gridOptions = useMemo<GridOptions>( () => ({ suppressColumnVirtualisation: false, suppressRowVirtualisation: false, // 首屏数据渲染完成后触发第一次列宽调整 onFirstDataRendered: () => autoSizeVisibleColumns() }), [autoSizeVisibleColumns] ); const data = useMemo<any[]>(() => { return createRandomGridData(100, 200, { resizable: true }); }, []); const onVirtualColumnsChanged = useCallback( (_event: VirtualColumnsChangedEvent) => debouncedResize(), [debouncedResize] ); return ( <div className="ag-theme-alpine" style={{ height: '98vh', width: '98vw' }}> <AgGridReact ref={gridRef} gridOptions={gridOptions} rowData={data[0]} columnDefs={data[1]} enableRangeSelection={true} enableFillHandle={true} suppressClearOnFillReduction={true} onVirtualColumnsChanged={onVirtualColumnsChanged} /> </div> ); };
补充说明
如果单元格使用了自定义渲染器、存在异步加载内容(比如图片、接口返回的文本),需要在对应内容加载完成后,手动调用
autoSizeColumns针对对应列重新计算宽度,否则会出现宽度匹配不准的问题。防抖时长可以根据表格数据量灵活调整,数据量越大可以适当拉长防抖间隔,优先保证滚动流畅度。
内容的提问来源于stack exchange,提问作者mottosson
相关产品推荐
相关产品推荐

