告别
getBoundingClientRect引发的 Layout Thrashing,浏览器原生接管浮层定位,性能提升不止一个量级
话不多说,直接上结论:VS Code 1.119 把 WebView 的定位机制从 JS 手动计算切换成了 CSS Anchor Positioning。这波优化直接解决了多 WebView 场景下的卡顿和位置错位问题,谁用谁香 🚀
1. 为什么 VS Code 突然"抛弃"了 JS 定位?
1.1 VS Code 干了什么?
先看官方 Release Note 里的核心变化:
- 以前 :JS +
getBoundingClientRect()手动计算每个 WebView 的位置,每次 resize、scroll、拆分编辑器时都重新计算。 - 现在:CSS Anchor Positioning,浏览器原生处理元素跟随关系,不再需要 JS 读取布局。
简单翻译:以前是靠 JS 量尺寸、算坐标、手动设
top/left;现在是 CSS 里写一句"这个元素跟着那个锚点",浏览器自动搞定。
1.2 什么是 WebView?为什么它是性能热点?
WebView 在 VS Code 里随处可见:
- 插件渲染的 Markdown 预览
- 内置的浏览器预览
- 终端里的图形化界面
- Git Graph、数据库管理工具等复杂插件
一个复杂项目里,可能同时存在 几十个 WebView 。当你拖拽拆分编辑器、滚动代码、或者调整侧边栏宽度时,所有 WebView 都要重新定位。以前用 JS 逐个计算,卡顿感直接拉满。
1.3 这一点一定要注意!(踩坑预警)
VS Code 本身基于 Electron,本质上是一个"浏览器里的应用"。Electron 的渲染进程里,频繁调用 getBoundingClientRect() 会触发强制同步布局(Forced Synchronous Layout),也就是 Layout Thrashing。
我之前在一个 Electron 项目中写过一个浮层工具提示,每次鼠标移动都重新计算位置,结果滚动时掉帧明显。排查了半天才发现是 读写布局交替 导致的。踩过的坑,能绕电脑桌三圈 😭
2. 传统浮层定位方案:成也 JS,败也 JS
2.1 "三步走"的老套路
几乎所有浮层定位(Tooltip、Popup、ContextMenu)都长这样:
javascript
const btnRect = button.getBoundingClientRect()
popup.style.left = btnRect.left + 'px'
popup.style.top = btnRect.bottom + 'px'
看起来就三行代码,问题在哪?
浏览器不是傻子,但它会被你逼疯 。当你读取 getBoundingClientRect 时,浏览器必须先重新计算样式 、重新布局,才能给你准确的坐标。这种"读一次 → 强制计算一次 → 写样式 → 再读 → 再计算"的模式,就是 Layout Thrashing。
典型流程帮你画出来了:
text
读取布局(getBoundingClientRect)
↓
浏览器强制 reflow(同步计算)
↓
JS 计算坐标
↓
写回样式(top/left)
↓
下一次读取又开始循环...

2.2 什么叫 Layout Thrashing?一个生活化类比
想象你在超市搬货:
- 老方案:你每搬一箱可乐,都要先停下手中的活,拿尺子量一下货架高度,再把箱子放上去。搬 10 箱,量 10 次。
- 新方案:货架高度是固定的,你提前量一次,后面直接搬,不用反复测量。
JS 每次调用 getBoundingClientRect 就像"量尺子",浏览器被迫暂停当前工作,计算完再继续。多个 WebView 同时量尺子,主线程直接卡死。

2.3 为什么 VS Code 特别容易踩坑?
普通网页可能只有 1-2 个浮层,VS Code 的场景极其复杂:
- 多视图拆分:你可以把编辑器拆成 3 列 2 行,每个窗格里的 WebView 都要独立定位。
- 高频事件:滚动、拖拽拆分条、改变侧边栏宽度 → 这些事件每秒触发几十次。
- Electron 本质仍是浏览器:它没有绕过浏览器的 reflow 机制,反而因为多进程架构,让布局计算更加昂贵。
VS Code 官方说:"Positioning the webview here was done using JS, which called getBoundingClientRect. This call ends up being relatively slow because it triggers browser style recalculations and relayouts."
一句话总结:不是 JS 慢,是强制 reflow 慢。
3. CSS Anchor Positioning:从"算坐标"到"声明关系"
3.1 核心思想(敲黑板)
text
以前:JS 测量位置 → 手动设置 top/left
现在:CSS 声明"元素跟随谁" → 浏览器内部自动布局
就像你跟朋友说"我跟着你走",而不是每走一步都拿出尺子量你们之间的距离。 浏览器就是那个最懂布局的"路痴克星",你只要告诉它锚点是谁、贴在哪边,它自动搞定。
3.2 三个核心 API(记笔记)
css
/* 1. 给锚点起个名字 */
.button {
anchor-name: --my-btn;
}
/* 2. 让定位元素声明跟随哪个锚点 */
.tooltip {
position-anchor: --my-btn;
/* 3. 用 anchor() 函数获取锚点坐标 */
left: anchor(right);
top: anchor(top);
}
| API | 作用 | 类比 |
|---|---|---|
anchor-name |
给元素起个锚点 ID | 给地标贴个标签 |
position-anchor |
告诉定位元素"你跟着谁" | 你说"我跟着那个地标" |
anchor() |
获取锚点的某条边位置 | 问"它的右边在哪?" |
3.3 浏览器到底做了什么?
以前你调用 getBoundingClientRect,浏览器被迫立刻计算布局。现在你用 CSS 声明关系,浏览器在 layout 阶段统一处理,不需要 JS 介入。
打个比方:
- 旧方案:每个浮层都像"插队"的乘客,非要司机当场算座位。
- 新方案:所有乘客提前说好"我跟谁坐一起",司机一次性安排完所有座位,高效不堵车。
这波优化,本质是把"运行时计算"变成了"编译时声明"。
4. 一个最小 Demo:对比新旧方案
4.1 JS 旧方案(又慢又容易出错)
html
<div class="btn">悬浮我</div>
<div class="popup" style="display: none;">我是浮层</div>
javascript
const btn = document.querySelector('.btn')
const popup = document.querySelector('.popup')
btn.addEventListener('mouseenter', () => {
const rect = btn.getBoundingClientRect()
popup.style.left = rect.left + 'px'
popup.style.top = rect.bottom + 'px'
popup.style.display = 'block'
})
问题清单:
- 每次 hover 都触发一次强制 reflow
- 滚动时如果没重新计算,浮层位置就错位
- resize 时需要监听事件重新算
- 多个浮层互相独立,无法保证同步
4.2 CSS 新方案(无 JS,自动跟随)
html
<div class="btn">悬浮我</div>
<div class="popup">我是浮层</div>
css
.btn {
anchor-name: --btn-anchor;
}
.popup {
position-anchor: --btn-anchor;
left: anchor(right);
top: anchor(top);
/* 默认隐藏,hover 时显示 */
display: none;
}
.btn:hover + .popup {
display: block;
}
亲测有效,可直接套用: 不需要监听任何事件,不需要 getBoundingClientRect,滚动、resize 全部自动同步。
4.3 进阶 Demo:hover 切换锚点
这个例子直接封神------一个元素可以动态跟随不同锚点:
html
<div class="btn-a">按钮A</div>
<div class="btn-b">按钮B</div>
<div class="anchor">被定位元素C</div>
css
.btn-a {
anchor-name: --anchor-a;
}
.btn-b {
anchor-name: --anchor-b;
}
.anchor {
position-anchor: --anchor-a;
left: anchor(center);
top: anchor(bottom);
}
/* 当 hover B 时,动态切换锚点 */
body:has(.btn-b:hover) .anchor {
position-anchor: --anchor-b;
}
演示效果:hover A 时,C 贴在 A 下方;hover B 时,C 瞬间移动到 B 下方。这在前端要手动实现,得写一堆事件监听和位置计算,现在一行 CSS 搞定。
5. 为什么 CSS Anchor Positioning 可能改变前端生态?
5.1 过去:Popper.js / Floating UI 统治浮层定位
几乎每个项目里,只要涉及到 Tooltip、下拉菜单、气泡卡片,你都会看到:
javascript
import { createPopper } from '@popperjs/core'
const popper = createPopper(reference, popper, options)
这些库内部干了什么?本质还是 getBoundingClientRect + 滚动监听 + resize 监听 + 手动设置 transform。功能强大,但代价是运行时开销和复杂的事件管理。
5.2 未来:浏览器原生接管
当 CSS Anchor Positioning 普及后:
css
.tooltip {
position-anchor: --target;
left: anchor(right);
top: anchor(center);
}
不需要引入任何第三方库,不需要写 JS,性能和稳定性直接拉满。
记住:工具的核心是简化工作,而非增加学习成本。浏览器原生能做的事,就别再引入一个 20KB 的库了。
6. 兼容性与现实情况(避坑指南)
6.1 当前支持情况
| 浏览器 | 最低版本 | 状态 |
|---|---|---|
| Chrome | 125+ | ✅ 完全支持 |
| Edge | 125+ | ✅ 完全支持 |
| Electron | 35+ | ✅ 完全支持 |
| Safari | 16.4+ | ⚠️ 部分支持(anchor-name 可用,但 anchor() 函数有限) |
| Firefox | 129+ | 🧪 实验性(需开启 layout.css.anchor-positioning.enabled) |
6.2 什么场景适合直接用?
✅ 推荐场景:
- Electron 桌面应用(VS Code 本身就是)
- 内部管理后台(用户统一用 Chrome/Edge)
- 移动端 WebView(Chrome 内核)
❌ 谨慎场景:
- 面向大众的官网(Firefox 用户会看到布局错乱)
- 强依赖 Safari 的项目(目前支持不完整)
6.3 如何做 fallback?(渐进增强)
css
/* 支持新特性的浏览器 */
@supports (position-anchor: --anchor) {
.popup {
position-anchor: --btn;
left: anchor(right);
}
}
/* 不支持的浏览器回退到 JS 方案 */
@supports not (position-anchor: --anchor) {
.popup {
/* 通过 JS 动态设置 left/top */
}
}
这一点一定要注意! 不要直接删掉旧的 JS 定位代码,先用 @supports 做特性检测,兼容老浏览器。
7. 总结
核心一句话
text
CSS Anchor Positioning 的本质,
是把"浮层坐标计算"
变成"元素关系声明"。
技术价值
- 性能提升:消除 Layout Thrashing,reduce 主线程压力
- 代码简化:从几十行 JS 变成几行 CSS
- 稳定性:不再有 resize/scroll 不同步导致的错位 bug
开发者启示
前后端生态的演进方向越来越清晰:浏览器正在逐步接管过去需要 JS 完成的 UI 逻辑。从 CSS Grid、Flexbox 到 Container Queries,再到现在的 Anchor Positioning,CSS 已经强大到可以替代大量布局类 JS 库。
技术的本质是解决问题,选择合适的工具,才能让自己从重复劳动中解放出来。