CSS anchor-positioning 如何相对 SVG 子元素定位?
结论
这个现象可以从 SVG 锚点和缩放场景下的浏览器兼容性入手排查。仅凭这段复现,无法断定 CSS anchor-positioning 永久禁止使用 SVG 子元素,也无法确认某个浏览器缺陷已经修复。
要让 SVG 图表的提示框在缩放后仍与图形对齐,可以用 getBoundingClientRect() 获取 <rect> 的视口坐标,再通过 position: fixed 定位 popover,避开浏览器直接解析 SVG 锚点的路径。
为什么普通 div 能定位,SVG rect 却可能失败?
anchor-name 的适用范围是生成 principal box 的元素。锚点定位还涉及锚点盒的尺寸、坐标转换和目标锚点的选择。普通 HTML 元素定位成功,不能直接证明浏览器同样支持 SVG 内部图形的锚点定位。CSS Anchor Positioning 规范
示例中的 width="1000" 与 viewBox 宽度 100 涉及从 SVG 用户坐标到实际显示坐标的映射。<rect> 的 x="20" 不表示它在页面中距左侧 20px。viewBox 会根据 SVG 视口和 preserveAspectRatio 建立相应的变换。SVG 坐标系统规范
观察到 Firefox 在不缩放时正常、缩放后异常,问题可能与 SVG 坐标转换或相关实现有关。不过,这只是排查方向,还不能据此确认浏览器存在缺陷。
另外,popover 的默认样式包含 inset: 0 和 margin: auto。锚点定位没有生效时,默认的居中布局可能重新显现,因此仅凭 Chrome 中看到的居中位置,无法判断失败原因。HTML 默认渲染规则
如何排查原有 CSS?
先使用同一浏览器中的 HTML 对照元素,确认基础语法是否可用:
const supportsAnchorPositioning =
CSS.supports('anchor-name', '--anchor-rect') &&
CSS.supports('position-anchor', '--anchor-rect') &&
CSS.supports('position-area', 'top');
console.log(supportsAnchorPositioning);
这个检查只说明浏览器接受这些声明,不能证明 SVG 子元素和 SVG 缩放定位正确。
然后分别测试 SVG 原始尺寸和缩放尺寸,保留 #circle 对照,并记录浏览器版本。如果普通 HTML 锚点正常,而 SVG 仅在缩放后错位,就可以把复现范围缩小到 SVG 的坐标映射场景。当前材料没有提供具体浏览器版本,因此无法给出可靠的版本支持表或修复版本。
如何用 JavaScript 可靠地定位?
以下方案适用于支持 Popover API 的浏览器。保留原 HTML、按钮和 popover="auto",用下面的 CSS 替换原来的 #popover1 规则:
#popover1 {
position: fixed;
position-anchor: none;
position-area: none;
inset: auto;
margin: 0;
transform: translate(-50%, -100%);
}
在 DOM 创建完成后执行:
const rect = document.getElementById('rect3');
const popover = document.getElementById('popover1');
const svg = rect.ownerSVGElement;
const gap = 8;
function placePopover() {
const bounds = rect.getBoundingClientRect();
popover.style.left = `${bounds.left + bounds.width / 2}px`;
popover.style.top = `${bounds.top - gap}px`;
}
popover.addEventListener('beforetoggle', (event) => {
if (event.newState === 'open') {
placePopover();
}
});
let frame = null;
function schedulePosition() {
if (!popover.matches(':popover-open') || frame !== null) {
return;
}
frame = requestAnimationFrame(() => {
frame = null;
if (popover.matches(':popover-open')) {
placePopover();
}
});
}
window.addEventListener('resize', schedulePosition);
document.addEventListener('scroll', schedulePosition, true);
const resizeObserver = new ResizeObserver(schedulePosition);
resizeObserver.observe(svg);
getBoundingClientRect() 返回视口坐标。CSSOM View 对 SVG 布局盒的边界计算要求应用元素及其祖先的相关变换,因此这些坐标可以直接用于相对视口定位的 popover,无需手动乘以 SVG 的缩放倍数。CSSOM View 规范
代码将 popover 的底边放在矩形上方 8px 处,并让两者水平居中。由于使用 position: fixed,不要再给这些坐标加 scrollX 或 scrollY。
使用时还要注意什么?
- 图表数据更新、
viewBox改变或内部图形移动时,应主动调用schedulePosition()。外层 SVG 尺寸不变时,ResizeObserver不一定触发。 - 如果图形持续动画,需要在动画期间持续更新位置;上面的事件监听不负责逐帧追踪。
- 示例没有实现边界避让。图形靠近视口顶部时,提示框可能超出屏幕,需要另加下方显示或位置约束逻辑。
getBoundingClientRect()给出的是变换后的轴对齐边界。对于旋转或倾斜图形,“边界顶部中央”未必是业务需要的几何点。- 不要直接把
getBBox()返回的 SVG 用户坐标填入 HTML 的left、top;若使用它,需要另做坐标转换。 - 组件卸载时,应移除事件监听并调用
resizeObserver.disconnect()。
备注:内容仅供参考。
