H5 DOM 更新与渲染优化
H5 DOM 更新与渲染优化
页面数据变化后,浏览器可能重新计算样式、布局、绘制并合成。DOM 节点数量、读写顺序和更新范围都会影响成本。优化目标不是避免所有 DOM 操作,而是减少无效更新,让一次业务变化只影响真正需要改变的区域。
一、先确认瓶颈在哪个阶段
卡顿可能来自 JavaScript 计算,也可能来自布局和绘制。性能时间线中应分别查看:
- 脚本执行时间。
- 样式计算范围。
- 布局次数和耗时。
- 绘制区域大小。
- 合成层数量。
如果主要成本是数据排序,重写 CSS 并不会带来改善。
二、批量创建节点
大量列表项可以先在脱离文档流的片段中构建,再一次加入页面:
function renderRows(rows: readonly RowModel[]): DocumentFragment {
const fragment = document.createDocumentFragment();
for (const row of rows) {
const element = document.createElement('div');
element.className = 'data-row';
element.textContent = row.displayText;
fragment.appendChild(element);
}
return fragment;
}
listContainer.replaceChildren(renderRows(models));
这适合整体替换。局部数据变化时,应更新对应节点,不必重建整个列表。
三、保持稳定业务键
框架列表渲染依赖键识别同一对象。使用数组下标作为键,插入一项后可能让后续节点状态错位。
type ListItem = {
id: string;
title: string;
};
键应稳定、唯一且来自业务标识。临时客户端对象也应在创建时生成一次标识,而不是每次渲染重新生成。
四、避免布局读写交替
读取尺寸可能要求浏览器先完成此前样式修改。循环中交替读写会造成多次同步布局。
const widths = cards.map(card => card.getBoundingClientRect().width);
cards.forEach((card, index) => {
card.style.setProperty('--card-width', `${widths[index]}px`);
});
先读取所有尺寸,再集中写入。更好的方案是让弹性布局或网格布局自行分配空间,完全避免脚本测量。
五、缩小状态订阅范围
一个全局对象任意字段变化都让整页重新渲染,会产生大量无效工作。组件只订阅自己需要的切片,派生数据使用稳定选择器。
不要把每个值都缓存。普通字符串拼接或小数组处理成本很低,过度记忆化会增加依赖比较和维护难度。
六、长列表选择合适策略
数据量较大时可以使用:
- 分页:一次只展示一个业务页面。
- 分段追加:用户滚动时逐步增加节点。
- 虚拟列表:只保留可视区域附近节点。
虚拟列表需要处理动态高度、焦点、滚动定位和读屏顺序。几百个简单节点性能正常时,不必为了技术复杂度强行使用。
七、隐藏与移除的取舍
隐藏节点可以保留内部状态,重新显示更快,但仍占用内存。移除节点释放更多资源,恢复时需要重建。
高频切换的小面板适合保留,低频且重型的页面可以销毁。决定应基于切换频率、状态恢复成本和内存表现。
八、控制观察器数量
尺寸、交叉和 DOM 变化观察器很有用,但为每个列表项创建多个观察器也有成本。可以让容器统一观察,或只在元素进入相关阶段时注册。
组件销毁后解除观察,回调中批量处理记录,不在每条记录里立即触发复杂布局。
九、动画避免触发布局
位移和淡入淡出优先使用变换与透明度。动画过程中修改宽高、边距和定位,会影响周围节点布局。
复杂阴影、模糊和大面积半透明也可能增加绘制成本,是否优化应通过真机时间线确认。
十、验证更新范围
可以构造固定数据集并记录:
- 修改单行时重新渲染的组件数量。
- 新增一项时 DOM 创建和销毁数量。
- 滚动过程的节点总数。
- 一次交互中的布局次数。
- 页面切换后的监听器和节点是否释放。
总结
DOM 优化的核心是减少无效工作。批量构建节点,使用稳定业务键,避免布局读写交替,并让组件只订阅所需状态。数据规模真正超过普通渲染能力时再引入虚拟列表,能在保持代码清晰的同时获得稳定性能。
- 点赞
- 收藏
- 关注作者
评论(0)