当前位置: 首页 > news >正文

性能优化学习

https://developer.chrome.com/docs/devtools/performance/selector-stats?hl=zh-cn

在 Chrome Performance 面板里,想看Layout(重排 / 回流)非常直观,我给你用最简单的方式讲清楚怎么看、怎么定位。

体验:https://googlechrome.github.io/devtools-samples/jank/

一、先打开 Performance

  1. F12 →Performance
  2. 点左上角● 录制
  3. 刷新页面 / 操作页面
  4. 点 ■ 停止,等待分析结果出来

二、怎么识别 Layout

主要看这 3 个地方:

1. 主面板颜色条(最直观)

  • 蓝色条:Loading(加载、解析 HTML/CSS)
  • 黄色条:Scripting(JS 执行)
  • 紫色条:Rendering→ 这里面就包含Layout
  • 绿色条:Painting(重绘)
  • 灰色条:System / Idle

只要看到紫色条特别长、特别多、特别碎,就是 Layout 太多、性能差。

2. 看 Main 线程任务里的标签

点开 Main 线程的火焰图:

  • 看到Layout字样 → 就是重排
  • 看到Forced Synchronous Layout→ 强制同步布局(最伤性能)
  • 看到Recalculate Style→ 重新计算样式(一般会跟着 Layout)

3. 看 Summary 统计

底部 Summary 面板:

  • Rendering时间占比高 → Layout / 样式计算多

三、重点:怎么看 “哪个 JS 导致了 Layout”

  1. 找到紫色的Layout任务
  2. 点开右边Call Stack(调用栈)
  3. 栈顶就是触发 Layout 的 JS 代码位置
  4. 点文件名,直接跳到源码

常见触发:

  • 读写 offsetWidth /clientHeight/getBoundingClientRect
  • 频繁改 style(width/height/top/left)
  • 频繁增删 DOM
  • 窗口 resize / 滚动

四、Forced Synchronous Layout(强制同步布局)

这是性能杀手,表现为:

  • 火焰图里出现密集小紫条
  • 任务名标红:Forced Synchronous Layout

原因:JS 一边读布局属性,一边改样式,浏览器被迫不停重排。

五、你关心 ECharts 多图表场景怎么看

多 ECharts 刷新时,你重点看:

  1. 是不是一堆密集紫色 Layout 条
  2. 是不是有Forced Synchronous Layout
  3. ECharts 容器是否频繁触发尺寸计算(resize)

如果用了transform: translateZ(0)优化后:

  • 紫色 Layout 条明显变少
  • 绿色 Paint 也会减少
  • 主要剩下灰色合成(Composite)

六、最简单判断标准

  • 紫色条少 → Layout 少 → 流畅
  • 紫色条多且碎 → Layout 爆炸 → 卡顿
  • 出现红色 Forced Synchronous Layout → 必须优化
操作类型触发的完整流程性能开销核心定义示例属性/操作
重排 (Reflow / 回流)Layout + Paint + Composite⚠️ 最高重新计算元素的几何属性(位置、大小),会触发后续所有渲染阶段,影响范围可覆盖整个页面宽高(width/height)、边距(margin/padding)、位置(top/left)、display(none/block)、字体(font-size)、添加/删除Dom元素,改变窗口大小
重绘 (Repaint)Paint + Composite⚠️ 中等仅重新绘制元素的外观属性,不改变几何位置,跳过布局计算颜色(color/background-color)、可见性(visibility)、透明度(opacity, 不提升合成层时)、边框样式(border-style)
合成 (Composite)Composite only✅ 最低仅在 GPU 层合并图层,不触发布局和绘制,是性能最优的操作transform、opacity(提升合成层后)、filter(部分)

仅触发合成(零开销,优先使用)

通过 GPU 硬件加速,仅在合成层操作,不触发重排 / 重绘:

  • transform(平移、缩放、旋转等,浏览器默认提升为独立合成层)
  • opacity(元素被提升为独立合成层后,修改仅触发合成)
  • 部分filter属性(如blur,需浏览器支持合成层优化)
  • will-change:提前告知浏览器元素将发生变化,主动提升合成层

提升合成层:用will-changetransform: translateZ(0)主动提升元素为独立图层,减少重绘范围

transform的优势:完全在合成层执行,不触发重排 / 重绘,是动画性能最优方案

合成层由 GPU 管理,过多独立图层会增加内存开销,需合理控制图层数量

合浏览器渲染原理和 ECharts 特性,我们可以通过主动提升合成层+合理使用 GPU 加速属性,大幅降低多图表页面刷新 / 渲染时的重排、重绘开销。


一、核心原理回顾

ECharts 的canvas渲染本质上是绘制在 DOM 元素上的位图,默认情况下,这些 canvas 会和页面其他元素共用合成层,每次刷新 / 重绘都会触发全页面的 Paint+Composite。通过以下方式,我们可以让图表容器独立为合成层,让后续的动画 / 更新仅触发 GPU 合成,不阻塞主线程:

  • transform:强制提升为独立合成层(浏览器默认优化)
  • opacity:配合合成层,修改仅触发 GPU 合成
  • filter:部分滤镜(如blur)可在 GPU 层执行,不触发布局重排
  • will-change:提前告知浏览器元素将发生变化,主动提升合成层

二、实战优化方案(含代码)

1. 图表容器:强制提升为独立合成层

给每个 ECharts 容器添加合成层触发样式,让浏览器为其分配独立 GPU 图层,避免重绘污染其他元素。

/* 方案1:使用transform主动提升合成层(兼容性最好) */ .echarts-container { /* translateZ(0) 强制开启GPU硬件加速,提升为独立合成层 */ transform: translateZ(0); /* 优化渲染性能,避免图像抖动 */ backface-visibility: hidden; perspective: 1000; } /* 方案2:will-change 提前告知浏览器(适合已知会频繁更新的图表) */ .echarts-container { will-change: transform, opacity; }
<!-- 每个图表容器都应用该样式 --> <div class="echarts-container" id="chart1"></div> <div class="echarts-container" id="chart2"></div>

2. 页面刷新 / 初始化时的性能优化

(1)批量初始化图表,减少重排次数

多图表同时初始化会导致浏览器多次重排,通过requestAnimationFrame批量执行初始化,合并重排:

// 错误示范:逐个初始化,触发多次重排 const chart1 = echarts.init(document.getElementById('chart1')); const chart2 = echarts.init(document.getElementById('chart2')); // 正确示范:批量初始化,合并重排 requestAnimationFrame(() => { const charts = []; // 所有图表DOM节点 const chartContainers = document.querySelectorAll('.echarts-container'); chartContainers.forEach((dom, index) => { const chart = echarts.init(dom); // 配置option... charts.push(chart); }); // 保存实例,后续更新使用 window.charts = charts; });
(2)避免图表容器尺寸重排

图表容器尺寸变化会触发重排,提前固定容器尺寸或避免动态修改宽高:

.echarts-container { width: 100%; height: 300px; /* 固定高度,避免JS动态修改height */ transform: translateZ(0); }

如果需要响应式适配,优先使用 CSStransform: scale()缩放容器,而非修改width/height

.echarts-wrapper { transform: scale(0.8); /* 仅GPU合成,不触发布局重排 */ transform-origin: top left; }

3. 图表更新 / 动画时:仅触发 GPU 合成

(1)数据更新优化:避免全量重绘

使用 ECharts 的增量更新 API,配合合成层,让更新仅在 GPU 层完成:

// 错误示范:setOption 全量更新,触发重绘+重排 chart.setOption(newOption); // 正确示范:仅更新变化的数据,减少重绘范围 function updateChart(chart, newData) { // 仅更新series数据,不修改其他配置 chart.setOption({ series: [{ data: newData, animationDurationUpdate: 0 // 关闭不必要的更新动画,减少主线程开销 }] }); }
(2)使用 opacity 实现淡入淡出动画(GPU 合成)

修改合成层上的opacity仅触发 GPU 合成,性能远优于修改visibility/display

.echarts-container { transform: translateZ(0); transition: opacity 0.3s ease; /* GPU层执行过渡,不阻塞主线程 */ }
// 显示/隐藏图表,仅修改opacity,不触发重排/重绘 function toggleChart(chartDom, show) { chartDom.style.opacity = show ? 1 : 0; }
(3)filter 属性的 GPU 优化使用

部分filter滤镜(如blur)可在 GPU 层执行,适合图表加载时的占位效果:

.echarts-loading { transform: translateZ(0); filter: blur(2px); /* GPU层执行模糊,不触发布局重排 */ opacity: 0.7; }

三、进阶优化:合成层管理与避坑

1. 避免合成层爆炸

  • 不要给所有元素都加transform: translateZ(0),过多合成层会增加 GPU 内存开销
  • 仅给频繁更新 / 动画的图表容器提升合成层,静态图表无需额外处理

2. 合成层优化的验证方法

使用 Chrome DevTools 查看合成层:

  1. 打开控制台 →More toolsLayers
  2. 查看每个图表容器是否被标记为独立合成层(Composited Layers
  3. 检查重绘区域:使用Rendering面板的Paint flashing,确认图表更新时仅自身区域重绘

3. 兼容性说明

  • transform: translateZ(0)兼容性:所有现代浏览器均支持,移动端也兼容
  • will-change:Chrome/FF 支持,旧版浏览器会忽略,不影响基础功能
  • filter:部分浏览器对filter的 GPU 加速支持有限,优先用于非关键路径

四、额外的 ECharts 性能优化(配合合成层效果翻倍)

  1. 渲染器选择:大数据量场景优先使用renderer: 'canvas',避免 SVG 生成大量 DOM 节点
    const chart = echarts.init(dom, null, { renderer: 'canvas' });
  2. 数据降采样:使用sampling减少渲染数据点,降低 canvas 绘制开销
    option = { series: [{ type: 'line', data: largeData, sampling: 'lttb' // 大数据采样优化,保留关键拐点 }] };
  3. 关闭非必要动画:禁用初始渲染和更新动画,减少主线程计算
    option = { animation: false, animationDurationUpdate: 0, tooltip: { show: false } // 非必要交互组件可关闭 };

五、完整示例:多图表页面优化模板

<style> .echarts-container { width: 100%; height: 300px; margin: 10px 0; /* 合成层优化 */ transform: translateZ(0); will-change: opacity; backface-visibility: hidden; } </style> <div class="echarts-container" id="chart1"></div> <div class="echarts-container" id="chart2"></div> <script> // 批量初始化图表,合并重排 requestAnimationFrame(() => { const chartContainers = document.querySelectorAll('.echarts-container'); const charts = []; chartContainers.forEach((dom, index) => { const chart = echarts.init(dom, null, { renderer: 'canvas' }); chart.setOption({ xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] }, yAxis: { type: 'value' }, series: [{ type: 'line', data: [120, 200, 150], sampling: 'lttb' }], animation: false, animationDurationUpdate: 0 }); charts.push(chart); }); // 后续更新时仅修改数据,不触发重排 window.updateCharts = (newDataList) => { requestAnimationFrame(() => { charts.forEach((chart, index) => { chart.setOption({ series: [{ data: newDataList[index] }] }); }); }); }; }); </script>

💡关键总结:合成层优化的核心是让图表容器成为独立 GPU 图层,后续的更新 / 动画仅触发合成,不阻塞主线程;同时配合 ECharts 自身的渲染优化,才能实现真正的流畅体验。

_01. 代码分离

1代码分离
默认情况下,所有的 JS 代码,包括业务代码、三方依赖以及 Webpack 所依赖的模块化代码都会被打包进入一个 bundle 文件中。当访问这个页面时,首先会下载这个页面的 HTML,然后进行解析,当解析到 script 标签时就会下载该标签 src 所引用的资源,由于所有的代码都在一个 bundle 文件中,所以该文件势必会非常大,就会造成白屏时间过长,严重影响首页的加载速度

解决单一 bundle 文件过大的方式就可以通过代码分离,使用代码分离可以按需加载或者并行加载这些文件,分离方式通常有:

  1. 动态导入:使用import()这种方式导入
  2. 多入口起点:使用 entry 配置手动分离代码
  3. 自定义分包:Entry Dependencies 或者 SplitChunksPlugin 去重和分离代码

1.1. 多入口依赖

通过配置对象形式的 entry,实现多入口。此时,对应的 output.filename 配置项中 filename 需要使用[]的形式来保证每个入口对应一个出口

module.export = { entry: { main: "./src/main.js", index: "./src/index.js" }, output: { filename: "[name].bundle.js"", path: reslove(__dirname, "./dist"), } }

使用多入口的弊端:

如果不同入口的文件依赖了相同的库或者工具函数,这些内容将会被各自打包(重复),解决方法:

  1. 通过额外配置 shared 属性表明共享的模块
  2. 将每个入口变为对象形式,并且增加 dependOn 选项
module.exports = { entry: { index: { import: "./src/index.js", dependOn: "shared" }, main: { import: "./src/main.js", dependOn: "shared" }, shared: ["axios"] } }

最终生成的打包结果中 index 中将会引入三个 bundle:

1.2. 动态导入

动态导入允许在代码执行过程中按需加载特定的模块,只有当模块被真正用到时,相关的代码才会被加载和执行。有两种动态导入的方式:

  1. 使用import()函数语法(导出的内容通过 import.then 中res.default()来获取)
  2. 使用 Webpack 已弃用的 require.ensure

index.js文件中通过import()动态引入 JS 文件

const button = document.createElement('button'); document.body.appendChild(button); button.onclick = () => { import('./router/about'); }

动态导入的模块会被单独打包到一个文件中,且 HTML 文件中是不会引入 src_router_about_js_bundle.js 的

包名称:

对于动态导入文件最终打包出来的名称,默认使用 filename 中设置名称规则。如果想对动态单独生成的包文件进行命名,可以在 output 中配置额外的 chunkFilename 来进行定义

module.export = { output: { clean: true, path: resolve(__dirname, "./dist"), filename: "[name]-bundle.js", // 只针对分包的文件命名,默认情况下获取到的 id 与 name 是一致的 chunkFilename: "[id]_[name]_chunk.js" } }

如果想要自定义名称,则需要使用魔法注释/* webpackChunkName: '' */进行命名:

button.onclick = () => { import(/* webpackChunkName: 'About' */ './router/about'); }

1.3. SplitChunks

第三种分包模式是 SplitChunks,底层使用了 SplitChunksPlugin 实现,在 Webpack5 中该插件已默认安装

默认情况下 SplitChunksPlugin 的默认值 async 只针对import()进行分包。但所使用的第三方库,例如 axios 即便导入和使用了,其库本身也会被放入主包

http://www.jsqmd.com/news/810840/

相关文章:

  • 异构摄像设备协同适配,适配工业车间复杂环境跨镜追踪管控
  • ORAN专题系列-8:5G O-RAN Option7分体式小基站硬件白盒化的关键组件与部署场景剖析
  • 终极指南:如何将UglifyJS完美集成到Python Web框架中
  • AMD Ryzen终极调试指南:免费开源工具SMUDebugTool完整解析
  • Origin绘图实战:7个高频问题与高效解决方案
  • 如何5分钟掌握Jump:从安装到高效使用的完整教程
  • 告别Fastboot连接烦恼:Win10系统最新通用USB驱动(Google官方版)下载与配置全攻略
  • 终极指南:10个实用技巧提升TIL项目代码质量的完整教程
  • Style2Paints终极色彩修复指南:如何快速修复AI上色中的局部色彩问题 [特殊字符]
  • 用 FFmpeg 实现 RTMP 推流直播
  • Atoll-OS实战:开箱即用的AI助手操作系统部署与深度定制指南
  • 芯片开发中的原型验证:从虚拟模型到FPGA原型的工程实践
  • Flutter 自定义绘制完全指南
  • 终极Powerlevel9k完全指南:10分钟打造专业级CLI开发环境
  • PowerToys中文汉化:让Windows效率工具真正融入中文用户工作流
  • Xshell6启动报错0xc000007b:从DLL缺失到Visual C++库修复的完整排障指南
  • 从航天服到立方星:ARISSat-1业余卫星的工程实践与教育使命
  • 终极指南:如何使用Gulf of Mexico轻松实现TCP/UDP网络通信
  • GoFrame gconv性能优化终极指南:5个减少反射开销的实用技巧
  • 如何快速掌握Truffle解码器:智能合约字节码解析的完整指南
  • Taotoken CLI工具一键配置团队开发环境实战指南
  • 为什么92%的Claude 3用户还没启用Haiku?:3分钟配置+5行代码解锁毫秒级响应
  • 保姆级教程:手把手教你用阿里云物联网平台创建第一个MQTT设备(附设备三元组详解)
  • 低成本离线电源EMI抑制实战:从共模噪声原理到无共模电感设计
  • 电路保护设计实战:保险丝选型、I²t计算与多级协同方案
  • AsyncDisplayKit滑动删除终极指南:10个技巧打造丝滑iOS列表体验
  • Vue.Draggable终极指南:掌握拖拽数据同步的5大核心策略
  • Botpress开源对话机器人平台:从架构解析到实战部署全指南
  • Dism++完整指南:Windows系统优化神器从入门到精通
  • 现代化权限控制终极指南:laravel-permission如何优雅实现枚举与通配符权限管理