1. 被老板盯上的那一刻我们得先分清“慢”到底是从哪来的我先说个真实的场景。某次上线前的演示老板随手点开我们刚做完的一个商品详情页图片一张张在手机屏幕上“撑开”视频首帧黑了两秒才出画面页面滚动起来头像和轮播图还在懒加载占位块里“打转”。老板没有骂人只问了一句这页面是怎么回事是不是你们代码写差了当时第一反应是辩解说图片体积太大、接口返回太慢、网络环境不好。但冷静下来看这个场景几乎是所有前端人都会撞上的两难时刻——我们经常被告知页面变快了但没有任何数据没有任何量化等老板亲自点开页面一切回到原点。其实老板口中的“慢”和工程师眼里的“慢”经常是两回事。老板看到的是视觉卡顿和等待感工程师往往只盯着接口耗时和打包体积。要解决问题先要把这两套感知对齐。这里先说一个铁律别急着改代码先定位。在动手优化之前我最常干的一件事是打开浏览器控制台把整个页面的资源加载爬一遍。你会发现四件事有的图片因为尺寸超大但是展示区域只有几百像素有的视频没有做任意预处理直接塞了个几十兆的原片有的字体文件动辄三五兆只是为了一两个标题字还有的是HTTP请求排队卡在了某个慢接口后面。每一种“慢”背后对应的解决办法完全不一样如果你上来就把所有图片一股脑转成WebP、把所有懒加载打开也许有效果但纯属碰运气。还有一件事需要提前统一认知优化不是一次性的。你这次把图片压缩了后面业务同学又上传了几张原图性能又回去了你加了缓存但文件名里没带hash用户永远拿到的是旧资源。所以这套组合拳不只是几个工具和几个命令更需要在流程上形成“防守机制”否则一切优化都会在两周后悄悄劣化。这篇文章就是按这条线来写的先诊断、再动手、最后用机制把成绩守住事后方方面面都有人问的时候你可以直接甩出一份数据报告。2. 先打针再吃药用一份体检清单把“慢”变成可量化的指标优化这件事最忌讳拍脑袋。我见过不少团队一听图片慢就开始压缩图片一听视频卡就开始换播放器最后老板再点一次问题还在。原因很简单没有把“慢”从感官变成数字。所以我给大家的第一板斧不是任何工具而是建立一套页面资源体检的流程。2.1 别用猜的直接看 Performance 面板和 Lighthouse打开 DevTools 的 Performance 面板录一段从页面开始加载到完全可交互的过程你会看到一个火焰图。重点关注几个指标LCP最大内容绘制、CLS布局偏移、TTFB首字节时间还有最关键的总传输体积。LCP 反映的是用户第一眼看到主要内容的时间对电商和内容站来说这个时间直接决定了用户是不是有耐心等下去。Lighthouse 则更“应试”一些会直接给你一个分数和建议列表。不过我提醒一句Lighthouse 的分数只能当参考它跑在模拟网络环境下和用户真实网络差距很大。我见过一个页面在 Lighthouse 里跑出 95 分但在弱网环境下一张首屏图足足转了四秒。所以真正要盯的是 Performance 面板里记录到的真实加载瀑布图那才是用户看到的真实世界。在体检清单里我每次必查以下几项图片请求数量和总字节数、视频文件体积和解码格式、字体文件数量、接口返回时间和后续资源的依赖阻塞。每一项对应一个优化方向。比如图片请求数量多需要做雪碧图合并或者改用CSS渐变总字节数大需要看是不是原图没有压缩字体文件大就要做子集化处理。2.2 给“体检单”一个参考基准心里才有数光知道当前数据还不够还得知道“多少算正常”。不同的业务形态标准不同但我提供一个经过多个项目检验的大致区间检查项健康范围需要警惕的警界线首屏图片总体积低于 300KB超过 800KB单张图片体积低于 150KB超过 500KB字体文件总体积低于 400KB超过 1MB视频首帧时间低于 1.5 秒超过 3 秒LCP低于 2.5 秒超过 4 秒总请求数低于 80 个超过 200 个这个表不是凭空捏的是按照主流移动端网络环境和设备性能试出来的。你拿去用的时候可以根据自己业务的用户群体稍微调整比如你的用户主要挂在公司WiFi下要求可以放宽一点如果是三四线城市用4G网络为主那就得把门槛设得更保守些。2.3 用 Network 面板揪出“隐藏的大佬”还有一个很实用的排查习惯按体积排序。在 Network 面板里按 Transferred Size 从大到小排列你会发现排在榜首的往往是几个根本没注意过的“隐形大佬”——某张背景图是设计直接拖进去的 2MB 原图某个图标是 SVG 转成 PNG 导出的超大文件某个视频连 poster 都没设置所以首帧黑屏。这个动作我建议每个前端至少每周做一次。不需要打开什么专业工具就是按一下列头排序就能对页面资源的健康状态了然于心。等模型变成习惯以后你自己就能预判哪些改动会让老板觉得页面变快了而不是每次都等他点开页面再被吓一跳。3. 静态图像把“一张原图走天下”的坏习惯改掉做完诊断之后接下来就是真正动刀的部分。先说图片因为大部分项目的多媒体体积大头都在这上面。图片优化的链条很长从设计稿产出、到上传、到服务器处理、到前端加载策略任何一个环节出问题都会让最终体验打折。前端最常背锅的是最后一公里——用户看到的图确实是当初上传的原图。所以我们需要一套针对前端可控范围内的完整策略每一步都得落实。3.1 格式选型WebP 和 AVIF 不是银弹但值得宠爱先解决容器问题。很多团队一直用JPG和PNGJPEG在照片上有不错的压缩率但透明通道缺失且不支持更先进的压缩算法。PNG在图标和截图场景很优秀但一旦用在照片上体积就很离谱。现代浏览器里性价比最高的格式是 WebP它既支持透明又支持动图体积通常比JPEG小30%以上而且兼容性已经覆盖了绝大多数现代浏览器。AVIF则是更新的选择压缩率还能再提升两成左右但解码耗CPU在低端安卓机上可能反而变慢。我个人的习惯是CDN上开WebP优先对不支持的环境自动降回原图AVIF暂时只在首屏大图上做增强不做全站默认。选型的时候别迷信“所有图片都转成AVIF就好了”这种简单结论。压缩率上去了但解压耗时也会上去尤其大图在低端机上有明显的“等图”从转圈变成卡顿的体验。WebP和AVIF可以交替用但必须做一次真机测试再上线。3.2 响应式尺寸 srcset别让手机端下载电脑端的图你有没有这种经历详情页里一张主图明明只在手机屏幕上占了375像素宽却加载了一整张1920像素的原图。移动端的流量就这样浪费掉了。这类问题的解法是给图片加上srcset和sizes属性。img srcproduct-640.jpg srcsetproduct-480.jpg 480w, product-800.jpg 800w, product-1200.jpg 1200w sizes(max-width: 480px) 100vw, (max-width: 768px) 50vw, 33vw alt产品主图 浏览器会根据当前的视口宽度、设备像素比和sizes里的计算逻辑自主选择最合适的资源不用前端自己写判断逻辑。这个方案老但实用很多人知道却懒得加。我负责过的一个商城项目仅靠加上srcset图片平均下载体积从220KB降到了90KB肉眼可见的加载加速。再补充一点服务端渲染时也可以根据User-Agent或Accept头直接在服务端生成裁剪图这种CDN处理式的方案更适合图片极其依赖运营上传的业务前端不用操心只需约定命名规范。3.3 懒加载和预加载不是把所有图都懒加载就完事了懒加载loadinglazy是最被滥用的优化手段之一。很多人把所有图片都加上这个属性结果首屏图和页面同步开始加载反而拖慢LCP——因为首屏图必须第一时间加载完你把它延迟掉等于自己给自己使绊子。正确的逻辑是首屏内的图片用fetchpriorityhigh立刻加载不要懒。首屏外的图片用loadinglazy滚动到附近才开始加载。部分“下一屏很可能会点开”的资源比如轮播图第二张、菜单里的大图背景可以用link relpreload asimage href...提前拉取减少用户操作后的等待时间。这里推荐一个关键原则懒加载要区分“楼层”。页面顶部3屏以内的内容都属于优先资源底部的内容才值得懒加载。如果整个页面一视同仁全部懒体验会变得非常割裂。我自己踩过一次坑把一个活动页所有商品图都加上了懒加载结果用户快速滑动的过程中图片是一个一个“追”着加载的滚动时全是占位块当时就被业务同学吐槽你们优化了个寂寞。3.4 压缩参数的实践参考图片压缩工具很多但压缩参数得心里有数。我用过的最稳妥组合是JPEG质量控制在72~78WebP质量控制在70~75PNG主要用来处理带透明通道的图标能用WebP替代的优先替代动图序列如果是纯展示直接转成WebP和视频用CSS方式播放。给一个可直接抄作业的压缩脚本示例用sharp实现const sharp require(sharp); async function optimizeImage(inputPath) { await sharp(inputPath) .resize(800, 800, { fit: inside, withoutEnlargement: true }) .webp({ quality: 75, effort: 4 }) .toFile(${inputPath}.webp); }这里的effort: 4是编码开销和压缩率的平衡点对中老年服务器来说不至于卡到不可接受。最后提一句“内置CDN图片处理”的思路如果你用的云厂商支持图像处理服务国内主流云基本都有那就把压缩、裁剪、格式转换都交给服务端前端只负责指定URL参数。团队内部约定好规则比如?imageView2/2/w/600/format/webp。这种方案的收益是巨大的——运营上传原图系统自动出多尺寸多格式前端不再需要乞求其他岗位同事手动压缩人力成本和沟通成本一起降下来。4. 体积大户视频、字体的“隐形重量”往往是压垮页面的最后一根稻草图片解决了并不意味着页面一定飞起。很多项目里视频、字体和音频才是真正的体积大佬。它们有一个共同特点平时很少有人主动关注体积因为它们通常不会像图片一样在Lighthouse里被批评结果老板一点开页面的时候它们都在暗中拖慢关键渲染路径。4.1 视频从转码开始别让用户下载你的原始素材视频优化最容易犯的错是把MP4原片直接交给前端。我见过一个神操作一个活动背景视频设计给的是1080P 60fps的原始素材直接200MB丢到项目里页面加载时间成功翻倍。视频的优化空间比图片还要夸张一个20秒的背景视频优化前可能25MB优化后能做到900KB肉眼几乎没区别。在项目里我通常建议按这个顺序处理视频转码到H.264编码兼容性好并保证渐进式播放moov原子前置。裁掉不需要的帧和分辨率。背景装饰类视频按720P输出就足够不需要1080P。去掉音频轨。大部分背景视频是无声的但很多素材默认带音轨白白增加了体积和网络占用。如果使用MP4务必设置poster属性。否则播放器在拿到首帧之前会是全黑的用户会以为视频没加载出来。FFmpeg是最常用的工具下面这个命令值得收藏ffmpeg -i input.mp4 -c:v libx264 -crf 28 -preset slow -movflags faststart -vf scale1280:-2 -an output.mp4crf在28左右的视频一般肉眼看不出明显劣化preset slow可以进一步压缩体积faststart让moov原子放到文件头部这样用户不需要等整个文件下载完就能开始播放。实测下来一条25MB的视频可以压到1.2MB左右代价只有一丁点锐度损失。还有一个进阶方案把循环类背景视频压成WebM再给不支持WebM的浏览器用MP4兜底。WebM在同等码率下画质明显好于MP4但编码耗时更高适合一次处理长期复用的场景。4.2 字体子集化 font-display让标题字不再拖慢白屏字体是另一个容易被忽略的“隐形重量”。中文网页尤其致命一个完整的中文字库动辄几MB要是你在页面里引入好几种字体用户还没看到内容就先下载了十几MB的字体文件。这些字体还阻塞渲染——浏览器为了等字体就绪宁可让文字不可见这就是FOITFlash of Invisible Text。前端侧能做的有三件事第一字体子集化。把字体文件裁剪出你页面实际用到的那几十个字符。中文场景下这个词叫“子集化”常用工具是fontmin服务端可以用pyftsubset。产品标题用的特殊字体通常只需要头、型号、颜色等几十个字体积从几MB降到几十KB不是梦想。第二设置font-display: swap。允许文字先用系统字体渲染字体文件加载完成后再替换。这样就算字体再大也不会阻塞页面文字展示。font-face { font-family: CustomFont; src: url(/fonts/custom.woff2) format(woff2); font-display: swap; unicode-range: U4E00-9FFF; }第三用woff2格式。WOFF2的压缩率比WOFF高约30%比TTF好更多。只要服务器支持一律优先WOFF2老浏览器再给woff兜底。我做过的某营销页项目之前全站加载了3个不同字体文件总大小2.8MB。子集化后总大小180KB页面文字秒出没有再出现白屏等待字体的惨状。4.3 音频和走位不定的GIF音频相对少见但一旦出现就很棘手。背景音乐如果必须放建议只放短音频时长控制在20秒以内循环播放用AAC或Opus编码。AAC是兼容性最好的Opus体积更小但老浏览器可能不支持。GIF则是另一个“经典陷阱”。一个小动画GIF画质差、体积大、解码慢三样占全了。我记得有一个项目里一个3秒的点赞动画GIF体积快6MB整个页面的性能被一颗“老鼠屎”毁了。后来的做法是转成WebP动图或者直接用视频标签循环播放体积直接从6MB降到300KB简直天壤之别。5. 传输层加速CDN、HTTP缓存、预连接把后台工作做到位多数的优化都集中在文件本身但这只是事情的一半。你再怎么压缩如果资源从服务器到用户需要跨越大半个网络或者HTTP缓存策略乱成一锅粥用户还是会有明显感知。传输层这一块前端能参与的地方比想象中多关键是别把自己局限在写代码里。5.1 CDN是底线不是加分项先说一个判断标准只要你的项目有外部用户访问就应当全站静态资源都走CDN。别怕花钱你没有CDN的时候每一次服务器带宽被打满损失的不只是用户的耐心还有可能搭上源站的稳定性。我对CDN选型的建议是优先选节点覆盖范围贴合你用户群体的服务商。如果用户集中在国内国内主流云服务商的CDN都够用如果用户分布在海外需要关注是否有海外节点并且支持HTTP/3。CDN的回源策略要设置为“回源时读取源站缓存策略”否则源站改了缓存规则CDN不跟着变一样白搭。5.2 缓存策略把该缓存的缓存住该失效的及时失效缓存是前端最容易被忽略、收益却又极大的手段。核心规则有两条第一文件名带hash的资源比如bundle.eda2f1.js设置永久缓存Cache-Control: max-age31536000, immutable因为hash变了文件名就变用户自然拉到新版本不会因为长缓存拿到旧文件。第二文件名不带hash的资源比如首页HTML、接口返回设置no-cache每次都要回源校验不得直接命中硬盘缓存。还有一点容易被忽略图片这类不常变化的资源后端应该在响应头里带上Cache-Control: max-age86400这样的短缓存而不是完全交给CDN处理。否则用户在弱网环境下每次滚动都会重新去加载已经看过的图片白白消耗流量。location /static/ { expires 30d; add_header Cache-Control public, immutable; }不过Immutable这个指令也遇到过兼容性问题有个别浏览器的老版本会把它当无效参数但我实测主流的现代浏览器都认可以放心加。5.3 预连接和预加载把连接建立的时间藏起来很多人不知道DNS解析和TLS握手其实要花不少时间尤其是在移动端点开一个冷站点的首屏时这几百毫秒的浪费会直接计在用户的等待头上。前端能做的是在HTML里加预连接提示link relpreconnect hrefhttps://cdn.example.com link reldns-prefetch hrefhttps://api.example.compreconnect会在浏览器空闲时提前建立连接dns-prefetch只提前解析DNS。这两行代码写起来很简单但减少的首屏时间往往能到300ms以上。之所以强调“预连接比预加载好用”是因为预连接不占带宽而预加载如果文件太大反而会挤占关键资源的带宽优先级把握不好就适得其反。6. 从“优化一次”到“一直优化”用性能和预算机制守住劳动成果最沮丧的事情不是你优化没效果而是你花一周把页面从6秒优化到2秒结果一个月后的某次迭代某位同事顺手往首屏丢了一张2MB的图页面又回到5秒。这不是同事的错没有人会故意破坏性能真正的问题是项目里缺少一种能自动拦截劣化的机制。6.1 性能预算把“不能超过多少”写进代码而不是写进口号性能预算应该像代码规范一样写进项目文档并且进CI检查。我习惯用size-limit或者bundlesize这套工具直接在打包时统计产物大小超出预算就报错打断发布流程。比大小更值得做的是统计页面核心资源的总传输大小。以我常做的活动页举例{ name: Landing Page, limits: [ { name: 首屏总资源体积, path: dist/assets/**, limit: 400 KB } ] }这个预算本地怎么定我建议按你现有页面测出来的数据打个七折作为第一阶段目标。比起一步到位要求别人“必须低于300KB”不如把门槛设得可以够到然后再逐步收紧。6.2 在CI里设置自动化检查让慢资源进不了主线以前我和某后端同学配合做性能优化最头疼的是每次发版前都要手动从Network面板截图对比费时费力还不严谨。后来我们把Lighthouse CI融进了GitHub Actions每次提交代码都自动跑一遍移动端模拟环境。脚本大致长这样npx lighthouse https://staging.example.com \ --budget.jsonbudget.json \ --chrome-flags--headless \ --outputjson \ --output-path./lighthouse-results.json然后在CI脚本里判断performance分数小于90就直接build失败。这招其实非常“狠”配置完之后业务和运营以后想往页面塞大图之前都得先掂量掂量因为一旦超标发布流程直接被卡住。设置这些规则的意义其实是把“性能是大家的事”这个认知变成一种制度化约束而不是某个人的单方面自觉。6.3 给团队留一个“性能安全走查表”CI毕竟只能拦截打包层面的问题拦不了所有运行时问题。比如某个第三方SDK悄悄在地球另一半拉了一个慢接口这种不是打包工具能发现的。所以我另外整理了一份“性能走查表”作为代码评审的必查项内容包括新增图片是否压缩过有没有省掉不必要的尺寸裁切新增字体是否需要子集化主接口返回数据是否超过100KB有没有该做分页却全量返回的情况第三方脚本是否真的出现在首屏能否延后加载新组件是否引入了重量级依赖这份清单不是一成不变的每个团队可以按自己的业务特点调整。核心思路是让“性能”变成提交代码前的一个潜意识动作而不是事后被打脸才发现。7. 优化效果怎么证明给老板看对比数据、弱网模拟以及那些最容易翻车的细节最后一步不是技术问题而是“汇报问题”。你把优化做完了效果很好但如果拿不出一份好看的对比数据老板对你的感知仍然是“这个人上次好像被问住了”。所以优化过程中从第一天起就要记录数据最后用数字说话。7.1 用同一台机器、同一个网络、同一个浏览器做前后对比很多团队的对比数据不靠谱就是因为环境变量没控制好。今天在公司WiFi测明天在4G测后天换了个手机这只后一天数字自然没得比。我的习惯是把优化前后的页面都部署到同样的环境用DevTools的Network面板设置成Slow 4G同时开启CPU 4x降速才能比较接近用户真实感知。记录以下几个数字就够了LCP时间、总下载字节数、请求数量、图片总大小、视频总大小、字体总大小。优化前后各录一版做成一个简单表格页面加载时间从几秒到几秒的对比要比任何口头解释都直观。我记得某次汇报我把某移动端页面的传输体积从2.8MB降到900KBLCP从4.8秒降到2.1秒以后老板的反馈从“页面是不是有问题”变成了“这次优化做得不错”。这就是数据的力量。7.2 最常用的翻车现场一CDN缓存导致测到旧文件优化完之后本地测试一切完美一到线上测就发现没变化八成是CDN或浏览器把旧版本文件缓存住了。别慌排查路径是先看Network面板里的响应头是不是命中了磁盘缓存再看文件URL里有没有带hash最后确认CDN刷新规则是否正常。我在项目上遇到过某云厂商CDN有一个默认的缓存时间规则源站设置了no-cache它也不理后来得在CDN控制台单独配置“不缓存HTML文件”才搞定。所以上线后务必验证一下线上确实加载的是新版本资源否则白忙一场。7.3 最常用的翻车现场二压缩后的图片出现肉眼可辨的劣化压缩率太高会导致边缘锯齿、色块和文字发虚尤其在电商图、文字图上很容易翻车。我的经验是压缩之前先看图片里有没有文字、有没有渐变、有没有大面积纯色这三类图压过头非常明显应在压缩参数上适当加码比如JPEG质量从72抬高到80WebP从70抬到75。另外能指定压缩工具对“感兴趣区域”保留更高画质时一定要利用比如领域内专门为图片压缩设计的AI服务实测在保文字清晰度方面表现极佳。我个人做法的准则是宁可体积大50KB也不要让用户一眼看出“图糊了”。在展示型页面上画质劣化的代价比加载慢0.2秒更严重。7.4 低成本高回报的隐藏招把首屏做成静态不止一次遇到这种场景页面首屏的电商大图、标题、按钮都来自接口动态返回用户点进来先看到骨架屏等接口好了图片才慢慢加载。而实际上这类数据大半天都不会变。做法很简单在服务端把首屏数据渲染成静态HTML返回或者用CDN的边缘函数缓存首屏的JSON数据。这项改动通常不需要前端写太多代码但首屏时间可以下降得很明显。这种“前端摸不着但要想得到”的思路才是真正的体系化优化。回到第一段那个场景最后老板点开页面露出满意的表情时我顺势补了一句这版首屏体积比上个版本小了一半。他也没再提页面卡的事。老板要的从来不是你看上去辛苦不辛苦而是页面是不是真的快了以及你能不能拿出让人信服的证据。最后再分享一个坚持了很久的小习惯每次优化完成不要把对比截图存进某个犄角旮旯而是整理成一两句话和一张表发到团队群里。等到下个月又有人问“是不是变慢了”的时候你就可以把这次记录当作基线一眼看出新变化发生在哪里。性能优化有终点吗没有。但至少能把这些曾经的坑一个个填平让踩坑的过程变得可记录、可追溯这样长期下来团队里每个人都会养成更好的敏感度业务方也会对你越来越放心。