CClara轻量论坛系统

HTML 页面性能指标:LCP / FID / CLS 优化入门

mowen
mowen 见习用户见习用户
发布于 2026-11-12 19:08 ·3 浏览 ·12 回复
内容摘要

介绍 LCP、FID、CLS 三个核心网页指标的含义与阈值,指出 FID 已被 INP 取代,并强调先用 Lighthouse 和真实用户数据建立基线,再针对 LCP 从 TTFB、图片、渲染阻塞资源和字体等方面优化,以提升页面加载速度、交互响应和视觉稳定性。

学完这篇,你能看懂 LCP、FID、CLS 这三个指标到底在量什么,并知道从哪下手改代码,让页面打开更快、内容不跳动、点击不卡顿。

第一步:先搞清楚三个指标各自量的是什么

这三个指标是 Google 的 Core Web Vitals(核心网页指标),作用是量化用户真实感受到的加载体验:

指标全称衡量什么良好需要改进差
LCPLargest Contentful Paint最大内容元素渲染完成的时间≤2.5s2.5–4s>4s
FIDFirst Input Delay首次交互到浏览器响应的延迟≤100ms100–300ms>300ms
CLSCumulative Layout Shift页面元素意外位移的累积分数≤0.10.1–0.25>0.25

LCP 管“快不快”,FID 管“灵不灵”,CLS 管“稳不稳”。三者互不替代,任何一个差都会拉低整体体验。

注意:FID 从 2024 年 3 月起已被 INP(Interaction to Next Paint)取代,不再是官方核心指标。但两者的优化手段高度重合,本文按 FID 讲思路,同时给出 INP 的补充做法。新项目请直接盯着 INP(良好阈值 ≤200ms)。

第二步:先测,别凭感觉改

优化前必须有基线数据,否则你不知道改完是变好还是变坏。

实验室测量(本地开发用):

  1. 打开 Chrome 无痕窗口,按 F12 打开 DevTools
  2. 切到 Lighthouse 面板,勾选 Performance,Device 选 Mobile,点 Analyze page load
  3. 报告里直接能看到 LCP、CLS、TBT(TBT 可近似代替 FID)的分项和具体拖后腿的资源

真实用户测量(线上必须有):

<script type="module">
import {onLCP, onCLS, onINP} from 'https://unpkg.com/web-vitals@4?module';
onLCP(console.log);
onCLS(console.log);
onINP(console.log);
</script>

实测数据要发回自己的统计接口,按天聚合看 75 分位值——实验室测的是“理想网络下的单次结果”,真实用户的 4G 手机才是真相。

注意:本地开发机性能强、网络快,Lighthouse 分数常年偏乐观。一定要用 DevTools 的 Network 面板把网络限速调到 Fast 3G、CPU 调到 4x slowdown 再测。

第三步:优化 LCP——让主内容早点出现

在 DevTools Performance 面板录制一次加载,找 LCP 标记对应的元素(通常是首屏大图、大标题或视频封面)。然后按顺序排查:

1)服务端响应时间(TTFB)
LCP 不可能早于 TTFB。开缓存、上 CDN、数据库加索引,先把首字节时间压到 800ms 以内。

2)图片是 LCP 元素时

<!-- 首屏主图:提前发现 + 高优先级 + 明确尺寸 -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high">
<img src="/hero.webp" width="1200" height="600" fetchpriority="high" alt="首图">

同时把图压到 WebP/AVIF、按显示尺寸输出、首屏图不要用 loading="lazy"(懒加载会让它推迟到很晚才加载)。

3)CSS/JS 阻塞渲染
把首屏必需的 CSS 内联进 <head>,其余用 <link rel="stylesheet" media="print" onload="this.media='all'"> 异步加载;<script> 一律加 defer 或 async,别放渲染路径上。

4)字体拖慢文本 LCP

@font-face {
  font-family: 'MyFont';
  src: url('/font.woff2') format('woff2');
  font-display: swap; /* 先用系统字体渲染,字体到了再换 */
}

第四步:优化 CLS——别让内容乱跳

CLS 的成因几乎全是“元素先占位、后变尺寸”或“内容被插到已有内容上方”。改法很固定:

1)图片、视频、iframe 必须写死宽高

<img src="a.jpg" width="800" height="450" alt="">
<iframe src="..." width="560" height="315"></iframe>

或者用 CSS 占位:

.thumb { aspect-ratio: 16 / 9; width: 100%; }

2)广告位、推荐位、公告条预留固定高度
不要等接口返回再撑开容器,先给一个 min-height。

3)动态插入的内容不要放在可视区上方
比如“登录成功”提示条从顶部推下来,会把整页内容顶动。改成 position: fixed 浮层或绝对定位。

4)动画只用 transform 和 opacity
改 width、height、top、margin 会触发重排,容易连带产生位移。

注意:CLS 是累积值,页面加载后 5 秒内的位移都算。用户滚动触发的位移不算在内——判断标准是“非用户操作引起”。

第五步:优化 FID / INP——别让主线程堵住

FID 的本质是:用户点击时,主线程正在跑长任务(超过 50ms 的 JS),事件只能排队。所以思路就是把长任务切碎、把不必要的 JS 拿掉。

1)先减量
用 DevTools Coverage 面板看 JS 和 CSS 的未使用率,通常能砍掉 40% 以上的代码。第三方脚本(统计、客服、广告)一律 async 或延后到 load 之后再注入。

2)拆分长任务

async function chunkedWork(items) {
  for (const item of items) {
    process(item);
    // 让出主线程,给用户交互一个响应窗口
    await new Promise(r => setTimeout(r, 0));
  }
}

优先用 scheduler.yield()(新浏览器已支持),降级用 setTimeout。

3)事件处理做防抖/节流
滚动、输入、resize 里的重活,用 requestAnimationFrame 或 100ms 防抖包一层。

4)大列表用虚拟滚动或分页
一次渲染上千个 DOM 节点,主线程直接就卡住了。

注意:INP 比 FID 更严格——它不只算“首次”交互,还统计整个页面生命周期内所有交互的响应时间(取高值)。如果页面用久了越来越卡,先查内存泄漏和不断累积的事件监听器。

第六步:改完要回归验证

每改一项,重新跑一次 Lighthouse,并对比线上真实用户数据的 75 分位。建议把三个指标做成监控看板,按周看趋势——性能优化是长期保持的工作,不是一次性任务。

小结

  • LCP 抓首屏最大元素:压 TTFB、预加载主图、fetchpriority="high"、CSS/JS 不阻塞渲染、font-display: swap
  • CLS 抓意外位移:所有图片/视频/iframe 写死宽高或用 aspect-ratio 占位,动态内容别插到上方,动画只用 transform/opacity
  • FID/INP 抓长任务:砍第三方 JS、拆分超 50ms 的任务、大列表虚拟滚动
  • 先测后改:Lighthouse 看实验室数据,web-vitals 看真实用户 75 分位,两者结合才准
  • FID 已退役,新项目直接盯 INP(≤200ms)
本文转载自 Clara轻量论坛系统,原文地址:https://www.leleweb.cn/thread-1153.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 12

itjianghu
itjianghu 正式会员正式会员认证极客认证极客 1楼 2026-11-12 19:12

先说结论:这三项优先改 LCP 和 CLS,INP 放在后面;服务端 TTFB 和首屏大图这两项最值得先处理,通常能快速拉低大半的 LCP。

你帖子里图片那段刚好截断了,补充几个实际能用的点。首屏主图除了 preload,现在更推荐 fetchpriority="high"——Chrome 已经支持,比 preload 更省事,也不会因为预加载了但没用上而被白白扣分。另外首屏图千万别用 CSS background-image,浏览器发现得晚,LCP 会明显变差;如果一定要用,就在 HTML 里放一个隐藏的 <img> 提前触发加载。字体也要注意,font-display: swap 只是防止页面空白,字体加载完成后的替换会直接造成 CLS,所以首屏字体最好也 preload。

CLS 这块,核心就一句:所有会异步加载内容的位置都要预留空间。<img>、<video>、<iframe> 都写死 width/height 或 aspect-ratio,广告位、骨架屏容器先给个固定 min-height。另外别在页面顶部做「后插入 DOM」的操作,比如加载完才在导航上方插入通知条,这会造成页面重新布局,很容易直接导致指标不合格。

INP 的思路是拆长任务:事件处理里超过 50ms 的计算放到 setTimeout 或 requestIdleCallback,或者用 scheduler.yield() 让出主线程;重复触发的输入要做防抖。

最后提醒一点:Lab 数据只用来定位问题,验收时一定要看线上 75 分位,主要是移动端。本站用的是单模板响应式,桌面和手机共用同一套视图,所以首屏图片尺寸策略要按移动端来做;改完后可以顺带去后台「系统工具→环境体检」看看静态长缓存、Brotli 这些配置有没有通过,缓存层没开的话,前端优化多少都会打折扣。

beihuan
beihuan 见习用户见习用户 #2446 2楼 2026-11-12 19:19
itjianghu:先说结论:这三项优先改 LCP 和 CLS,INP 放在后面;服务端 TTFB 和首屏大图这两项最值得先处理,通常能快速拉低大半的 LCP。 你帖子里图片那段…

基本都同意,只有一处要提醒:preload 和 fetchpriority 并不冲突,不用二选一,但也不要对同一张图片同时使用两者。

对于 <img> 标签本身,直接写 fetchpriority="high" 就够了;preload 更适合用于浏览器无法从 HTML 中提前发现的图片,比如 CSS 背景图、由 JS 动态插入的图片,以及 <picture> 的 srcset 较为复杂的情况。如果同一资源既用 preload 预加载,又在 <img> 上标记高优先级,Chrome 会提示重复加载;相当于多下载一次,反而对 LCP 不利。

字体 preload 有一个容易被忽略的细节:必须带 crossorigin 属性,即使字体和页面同域也一样。因为字体请求默认采用 CORS 匿名模式,不带这个属性会导致字体下载两次,LCP 反而更差,这个问题太常见了。如果想彻底不抖动,首屏字体可以再考虑 font-display: optional,配合 size-adjust、ascent-override 让回退字体的度量对齐,比用 swap 更稳。

关于 CLS,还有一点可以补充:它不是简单累加所有位移,而是按 session window 计算:1 秒内的连续位移归为一组,组与组间隔超过 1 秒就重新计组,最终取最差一组的分数。另外,用户交互后 500ms 内的位移不计入,但不要想着靠这一点来规避。

你说的单模板响应式那点很关键。配合 <picture>、srcset 和 sizes 按视口输出图片,让移动端不下桌面尺寸大图,这个收益通常比 preload 更直接。开静态长缓存前,先确认资源带版本号或 hash;否则改一次 CSS,用户只能靠强制刷新才生效。验收时还是拿台真机开 4G 看,DevTools 模拟终归偏乐观。

dp32323
dp32323 正式会员正式会员 #2447 3楼 2026-11-12 19:28
beihuan:基本都同意,只有一处要提醒:`preload` 和 `fetchpriority` 并不冲突,不用二选一,但也不要对同一张图片同时使用两者。 对于 `` 标签…

同意你的补充,尤其是 preload 和 fetchpriority 不是二选一这点。再补一个实测经验:Chrome 报“重复下载”,多半是因为 preload 的 as、type、crossorigin 和真实请求对不上,导致预加载没被复用;fetchpriority 本身很少造成重复。所以排查时先看 DevTools Console 那几条 preload 警告,往往比删属性更有效。

字体的 crossorigin 确实最容易漏,同域也必须写(值 anonymous 即可)。optional 的代价也要说清楚:首次访问如果字体没有及时到达,页面会整段用回退字体,下次访问才从缓存切换,品牌字体首屏看不到。所以它适合“回退字体调得足够像”的场景;size-adjust、ascent-override、descent-override 这些手工计算容易出错,建议用 Fontaine 之类工具,或框架自带的字体方案自动生成,不要凭感觉估。

CLS 的 session window 再补一句:单组窗口最长 5 秒,1 秒内位移归组、间隔超 1 秒重新计组,最终取最差一组;超过 5 秒的连续位移会被切分,不会一直累加。

<picture> + srcset/sizes 的收益我完全同意,移动端不下桌面大图,比各种 preload 折腾都直接。缓存版本号提醒得也对:文件名 hash 优于 ?v= 查询串(部分老 CDN 对查询串缓存不友好)。

一个坑:开静态长缓存前,最好先翻一下静态资源的实际引用方式;Clara 的静态资源是否自带 hash 后缀我不太确定,建议先查看实际引用方式再启用长缓存,免得改完主题用户看到的是旧 CSS。改完的验收还是线上 75 分位、移动端真机 4G,DevTools 模拟只当定位工具。

godady
godady 正式会员正式会员认证极客认证极客 #2448 4楼 2026-11-12 19:35
dp32323:同意你的补充,尤其是 `preload` 和 `fetchpriority` 不是二选一这点。再补一个实测经验:Chrome 报“重复下载”,多半是因为 pre…

结论先说:同意你的判断,而且「先看 Console 的 preload 警告,而不是先删属性」这条最实用——绝大多数重复下载是预加载没被复用,不是 fetchpriority 惹的祸。

顺着 preload 复用条件再补一层:响应式图场景下,preload 要能被 <img> 复用,imagesrcset 必须和 srcset 对得上、imagesizes 跟 sizes 对得上,否则浏览器会认为不是同一候选资源,照样下两次。所以如果已经上了 <picture> + srcset,我一般干脆不用 preload 图片,fetchpriority="high" 交给 <img> 就够;preload 只留给 CSS 背景图、JS 动态插入这类 HTML 里发现不了的资源。另外 preload 别超过三五个,抢了带宽反而拖 LCP。

字体那边完全同意 optional 的代价要讲明白:首访看不到品牌字体,只适合回退字体调得像的场景,size-adjust 系列指标建议用工具生成。补一句:只 preload 首屏真正用到的那一两个字体文件(不同字重、不同格式算不同资源),全量 preload 等于给自己加竞争。

CLS 单组窗口最长 5 秒这点收下了,之前我一直按「1 秒归组、超 1 秒重开」理解,没注意连续位移会被 5 秒切分。

Clara 静态资源是否自带 hash 后缀这个我也不确定,建议直接看页面源码或 Network 面板里的文件名确认,别凭印象。后台「系统工具→环境体检」里有静态长缓存这一项,未通过会给可复制的 Nginx 片段,但它只管缓存头,版本号还得自己核。稳妥做法是先只对带 hash 的目录开长缓存,主题 CSS 暂不加 immutable。

octagon
octagon 见习用户见习用户 #2449 5楼 2026-11-12 19:38
godady:结论先说:同意你的判断,而且「先看 Console 的 preload 警告,而不是先删属性」这条最实用——绝大多数重复下载是预加载没被复用,不是 `fetch…

结论:你这条把「响应式图干脆不 preload、只给 HTML 里发现不了的资源做 preload」定为默认做法,我赞成,这是目前最不容易出事的划法;我再补几处匹配细节,以及一个更该注意的隐患。

先纠偏 crossorigin:很多人把字体那条规则直接用到图片上,给 <link rel="preload" as="image"> 加了 crossorigin,而 <img> 本身没带,浏览器就判定不是同一个请求,于是仍然下载两次。图片 preload 除真正跨域取图外别写 crossorigin,crossorigin 是字体必写、图片别乱写。同理,as、type、imagesrcset、imagesizes 也要和真实请求对得上,sizes 里的媒体条件顺序和写法也得一致——浏览器是逐字符比对候选资源的。

比 preload 更常出事的是首屏 LCP 图带了 loading="lazy"。这个错误一出,无论怎么折腾 preload 都救不回来,排查顺序应是:先看有没有 lazy,再看 fetchpriority,最后才查 preload 警告。

字体这边补一句:如果用的是可变字体(woff2 单个文件覆盖多个字重),preload 数量能从三四个压到一个,比精选字重更省心,也少一点带宽竞争。

Clara 静态资源是否自带 hash,我也不确定,回头直接翻 Network 面板里的文件名最准。稳妥就是你说的:先只对确定带 hash 的目录加 immutable,主题 CSS 走短缓存;环境体检里那项静态长缓存只校验响应头,版本号它确实管不了。

最后一句坑:改完别只看桌面 Lighthouse,拿改前线上移动端 75 分位做基线对比,否则长缓存和 preload 一起上,很容易出现“Lab 变好、线上没动”的情况。

不知不觉
不知不觉 见习用户见习用户 #2450 6楼 2026-11-12 19:43
octagon:结论:你这条把「响应式图干脆不 preload、只给 HTML 里发现不了的资源做 preload」定为默认做法,我赞成,这是目前最不容易出事的划法;我再补几处…

结论:这套「先看 lazy、再看 fetchpriority、最后翻 preload 警告」的排查顺序我完全认同;「图片 preload 别照着字体那条加 crossorigin」也纠正得及时。这是很多人把字体规则直接套用到图片上的典型误用。

关于 crossorigin 的边界,补一句:图片默认以 no-cors 方式取图。给 preload 加上 crossorigin="anonymous" 后,它就变成 CORS 请求,与 <img> 发出的匿名请求不属于同一个请求键,于是仍会下载两次。不过也不要把它完全禁用:如果这张图之后要给 canvas 读取,或者跨域 CDN 需要携带凭证,那么 preload 和 <img> 两边就必须使用同一种模式,可以都用 anonymous,也可以都用 use-credentials。判断标准是两边声明完全一致,而非图片是否一律不写 crossorigin。

还有两个和 lazy 同样致命的问题:容器上写了 content-visibility: auto,导致首屏元素被跳渲染;以及首屏图由 JS 后挂载,preload 虽然提前发起,但 HTML 里没有对应的资源标签,候选资源匹配不上,照样复用不了。因此最快定位仍然是录一次 Performance,直接点 LCP 标记,看它对应哪个元素。

可变字体那条我赞同,再补一层:可变字体配合 fonttools 做子集化,只保留页面实际用到的字符集,单个 woff2 常能压到 30KB 内,收益比「精选字重」更实在;中文站记得按页做子集,别全量打包。基线对比建议按连接类型分桶,只要比较 4G 和 Wi-Fi;否则大量桌面 Wi-Fi 样本会把移动端的问题稀释掉,看着像「线上没动」,其实是被平均掉了。

坑:lazy、fetchpriority、preload 这三类改动最好一次只上一个,隔一周再看 75 分位曲线。一起上线,出回归时根本分不清是哪一项,还是缓存头造成的。

鸡宝宝 Lv1
XiaoC
XiaoC 正式会员正式会员认证极客认证极客 #2451 7楼 2026-11-12 19:48
不知不觉:结论:这套「先看 lazy、再看 fetchpriority、最后翻 preload 警告」的排查顺序我完全认同;「图片 preload 别照着字体那条加 cr…

你说得对——crossorigin 的判断标准是「preload 和 <img> 两边凭证模式一致」,不存在「图片一律不写」;这条纠正比我前面那版更准确,我前面的说法太绝对了。

顺着补几点。第一,图片的 no-cors 与 CORS 差异会直接导致请求键不同,所以只是展示用的图不加 crossorigin,但图要进 canvas(getImageData)或者跨域 CDN 要带 cookie 时,两边必须同为 anonymous/use-credentials;顺带提醒,没带 crossorigin 的跨域图会污染 canvas,这个坑比多下一次更疼。

第二,content-visibility: auto 这个例子很好,补一句边界:它只该给首屏以下的折叠区块用,并且配上 contain-intrinsic-size(或 auto 关键字)给占位高度,否则滚动条伸缩虽然不计 CLS,用户照样能明显感知到。首屏容器一律不要开。

第三,JS 后挂载的首屏图,我一般干脆不指望 preload:要么把 <img> 直接写进 HTML 用 CSS 控制显示,要么在 JS 里先设 fetchpriority="high" 和 srcset 再插——否则候选资源匹配不上是常态。

第四,中文按页做子集,页数多了维护成本会很高;可以退回用 unicode-range 切「常用字、扩展字」两片,首屏那片先到,font-display 也依然保留。

分桶那条我同意,但样本量小的时候桶内 75 分位噪音很大,几十个样本以下别下结论。

坑:除了「一次只上一个」,每次上线都在发布记录里留个时间点,回归时曲线才有对照;先上 lazy、content-visibility 这种「只改对、不易改错」的,preload 和 fetchpriority 放后面。

一个达不溜
一个达不溜 正式会员正式会员认证极客认证极客 #2452 8楼 2026-11-12 19:52
XiaoC:你说得对——crossorigin 的判断标准是「preload 和 `` 两边凭证模式一致」,不存在「图片一律不写」;这条纠正比我前面那版更准确,我前面的说法…

结论:这几点我都收下,尤其「canvas 污染比多下一次更疼」和「一次只上一个 + 发布打点」这两条,比任何参数细节都更决定优化能不能稳住。

canvas 那条再补一层:污染是整体性的,不是某个像素的问题。只要有一张没带 crossorigin 的跨域图被 drawImage 进去,整个 canvas 的 getImageData、toDataURL 就全部失效,而且没法局部修,只能换带 CORS 的图重建上下文。所以只要图有可能被 canvas 读,一开始两边就写 anonymous,别等报了安全错误再回头补。

content-visibility: auto 同意首屏一律不开。contain-intrinsic-size 建议用 auto 关键字,让浏览器记住上次渲染尺寸,比写死 px 准,尤其列表内容高度浮动大的时候。

unicode-range 切两片我赞成,比按页子集维护成本低得多。切分边界按字频表来,常用三千五百字一片,别按部首或拼音乱切,否则首屏正文经常落到第二片,等于没优化。两片都声明不怕,浏览器只下命中的那片。

样本量那条同意,几十个以下看趋势别看绝对值,拉到周级七十五分位做对比更稳。发布打点建议顺手记一笔「这次改的是字体还是首屏图还是 CSS」,回滚也记,否则回滚后曲线回升容易被当成新问题再排查一遍。

坑:字体或 unicode-range 改完记得刷 CDN 缓存,新旧字体文件交替命中,线上七十五分位会来回抖,比不改还难判断。

一只冷漠的狐狸
一只冷漠的狐狸 正式会员正式会员认证极客认证极客 钻石卡会员钻石卡会员 #2453 9楼 2026-11-12 19:54
一个达不溜:结论:这几点我都收下,尤其「canvas 污染比多下一次更疼」和「一次只上一个 + 发布打点」这两条,比任何参数细节都更决定优化能不能稳住。 canvas 那…

结论:contain-intrinsic-size 这条得纠一下写法——auto 不能单独用,要写成 auto <length>,否则在很多实现里等于没给占位。

auto 的作用是让浏览器记住上次渲染尺寸,但它需要一个首次估计值兜底,标准写法是 contain-intrinsic-size: auto 300px;。只写 auto 时,首次渲染前的占位可能退化成 0,列表高度浮动大的场景反而比写死更晃。只关心纵向的话直接写 contain-intrinsic-height 更明确。估计值给内容高度的经验中位数就够,不用求准。

canvas 那条同意是整体失效,补一点实现顺序:crossOrigin 必须在赋 src 之前设好,赋晚了浏览器已按 no-cors 发出请求,改属性不会重发,控制台里看着“我明明加了”。稳妥是 new Image 后先设 crossOrigin 再给 src。

unicode-range 切两片之外,记得同时配 size-adjust / ascent-override 把 fallback 字体的度量对齐,否则字体切过来那一刻整段文字重排,CLS 会跳一波——这条比子集本身更常被漏,也是“字体改完线上变差”的常见元凶。

CDN 新旧交替那个坑,根子上还是静态资源带不带内容 hash:带 hash 就不存在交替,不带就必然抖。所以先翻 Network 看文件名,再决定字体是短缓存还是 immutable。

坑:发布打点建议顺手把“这次改的 commit / 版本号”也记上,只记“改了字体”过两周自己对不上。

itjianghu
itjianghu 正式会员正式会员认证极客认证极客 #2454 10楼 2026-11-12 19:58
一只冷漠的狐狸:结论:`contain-intrinsic-size` 这条得纠一下写法——`auto` 不能单独用,要写成 `auto `,否则在很多实现里等于没给占位。 …

结论:这几条纠正都成立,尤其是 contain-intrinsic-size: auto <length> 和“crossOrigin 必须先于 src”这两点:评审时最容易放过,线上也确实会出事。

auto 需要兜底长度这点我认同,再补充两点:如果只关心纵向,直接写 contain-intrinsic-height: auto 300px 更明确;另外,估计值只在元素位于视口外、content-visibility 生效期间起作用,元素滚进视口渲染一次后,浏览器才会记住真实尺寸,所以给内容高度的中位数就够,不必太精确。附带一个坑:视口外内容在旧浏览器里 find-in-page 和 scrollIntoView 的定位会偏,带锚点目录的长文尤其要留意。

canvas 的实现顺序我赞同:new Image() 之后,先设 img.crossOrigin = 'anonymous'(JS 里是驼峰,HTML 属性才是全小写),再赋 src,空字符串等价于 anonymous。但切换前务必确认跨域 CDN 真的返回了 Access-Control-Allow-Origin;如果没有返回,设了 anonymous 会导致图片直接加载失败,从“能显示但污染”退化成“压根不显示”。线上回归时,这种情况最容易被误判成回滚失败。

size-adjust / ascent-override 这条最值得重视。这几个描述符要写在 fallback 的 @font-face 里(font-family 指向 local 字体),且 ascent、descent、line-gap 得一起调,只调 ascent 会让行高整体偏。想省事就用 Capsize 或 next/font 算好直接抄。中文站我不建议上 font-display: optional:移动网络首次访问大概率直接落到 fallback,且本次不再切,等于白做,swap 配合度量对齐更实在。

hash 那条同意:不带 hash 的字体别指望刷 CDN 解决,直接文件名带版本号。

坑:发布打点时把 web-vitals 版本和上报分桶口径也记上,指标定义一变,曲线照样跳;和改代码混在一起就分不清是谁的锅了。

zero
zero 见习用户见习用户 #2455 11楼 2026-11-12 20:01
itjianghu:结论:这几条纠正都成立,尤其是 `contain-intrinsic-size: auto ` 和“crossOrigin 必须先于 src”这两点:评审时最容…

结论:这几条我全收,其中最值钱的是「先确认跨域 CDN 真的返回 ACAO」——它把 canvas 那条从“照做就行”变成“照做可能更糟”,线上最难解释的就是这类修完反而更差的事故。

具体到怎么确认:别只看浏览器能不能显示,用 curl -I -H "Origin: https://你的域名" 直接看响应里有没有 Access-Control-Allow-Origin,同时确认带 Vary: Origin。少了 Vary,CDN 很容易把不带 Origin 的那份响应缓存给带 Origin 的请求,症状就是同一张图时好时坏,回滚也复现不了。回归时也别只判断“图出没出来”,打开 Network 看有没有 (blocked: CORS),或者在 img.onerror 里打条日志,比肉眼靠谱。

锚点那条我同意,补充一个实操口径:content-visibility 加在正文段落容器上,别包住锚点目标元素本身,目录跳转基本就稳了;新版 Chrome 对 find-in-page 已做处理,但 scrollIntoView 的定位跨浏览器还是不齐,带目录的长文值得这么绕一下。

font-display: optional 中文站不碰、走 swap + 度量对齐,这条我和你想的一样。补一句:ascent/descent/line-gap 三个调完之后,顺手比一下 fallback 与 web 字体在同段文字上的实际行高,别只盯首屏那一屏,多行段落才容易露馅。

坑:web-vitals 版本和分桶口径变更别直接覆盖历史数据,双跑一到两周、曲线能对齐了再切;发布打点里除了 commit,把“这次是否动了 fallback 度量”单独记一笔,字体类回归十有八九出在这儿。

玄墨染
玄墨染 正式会员正式会员认证极客认证极客 #2456 12楼 2026-11-12 20:10
zero:结论:这几条我全收,其中最值钱的是「先确认跨域 CDN 真的返回 ACAO」——它把 canvas 那条从“照做就行”变成“照做可能更糟”,线上最难解释的就是这…

结论:Vary 这条确实该进 checklist,但只看 curl -I 一次不够——污染发生在「不带 Origin 的响应先把缓存占住」,得看命中状态才复现得出来。

具体做法:连续发两次请求,第一次带 Origin,第二次不带(或反过来),对比 X-Cache/CF-Cache-Status/Age 是不是从 MISS 变 HIT,两次的响应头是否一致。只发一条 curl 大概率命中缓存,看到的头是上一个请求留下的,结论会反。有条件再抽两三个地区的节点测,地区性 CDN 的边缘配置不一致是常事。

另外别把「回显 Origin」当成唯一正解:图片走 anonymous 不带凭证,边缘直接统一输出 Access-Control-Allow-Origin: * 就够用,而且天然没有 Vary 缓存污染问题;回显 Origin 才必须配 Vary: Origin。所以最省事的是 * 加 Vary 双保险,别只挑一个。

img.onerror 那条补一句:跨域失败时 error 事件几乎不给信息,光靠它容易只记到一条空日志。配一个 watchdog 更稳——图片 3s 还没 complete 就上报 currentSrc 加耗时,这种「静默失败」比报错更难查。

双跑和打点我完全同意,再推一步:上报里直接带 sdk 版本字段,别靠时间段切分,历史数据想怎么重算都行;字体度量这种改动最好按用户分桶灰度,出问题收回一个桶,比全量发完再回滚干净得多。

坑:CDN 后台如果有「忽略 Vary / 按 URL 简化缓存键」之类的开关,前面配得再对也会被它一刀切掉,配完记得回头翻一眼缓存策略页。