要移除 SVG use 实例周围的意外空白,请确定它来自源 viewBox、宽高比适配,还是外围 CSS 行框。本指南将介绍如何诊断并修复各种原因,同时让定位坐标保持可预测。

这是一项简单的桌面网页编辑任务:在 SVG 内精确放置可重复使用的形状,然后让周围的 HTML 布局按预期适配。你可以在 Mac 上使用纯文本编辑器查看示例,并在当前的桌面浏览器中打开保存的 HTML 文件。

哪种坐标系决定了空白?

需要检查三个边界:外层 SVG 元素的 CSS 盒、被引用符号使用的视口,以及符号内部的实际形状。W3C 明确区分 SVG 视口和浏览器的 CSS 视口。viewBox 会将 SVG 用户坐标映射到 SVG 视口;它描述的不是浏览器窗口。坐标系、变换和单位 — SVG 2

例如,x="20" 用于 use 元素时,会指定 SVG 坐标系中的偏移量。这不表示距浏览器窗口左边缘 20 个 CSS 像素。缩放和祖先变换会改变该偏移量在页面上的显示位置。SVG 规范将 use 定位描述为额外的平移,其行为与 CSS 盒布局不同。文档结构 — SVG 2

先查看以下决策表。这是根据已记录的布局机制整理出的诊断顺序,并非一组可以互换使用的修复方法。

观察到的现象 首先检查 可能的修正方法
整个 SVG 下方有一条窄空隙 行内格式和父元素的行高 将独立 SVG 设为块级,或调整其行内对齐方式
每个重复图形内部都有空白 形状坐标与符号的 viewBox 如果内缩并非有意设计,则收紧源边界
符号实例两侧都有额外空白 实例尺寸与 viewBox 的比例 让两者比例匹配,或明确选择适配策略
局部位置正确,但页面位置不对 SVG 用户单位与浏览器视口坐标 在设置 x 和 y 前转换坐标
更改实例尺寸没有效果 href 所针对的元素 使用定义视口的符号来适配,或变换几何图形

使用完整的本地示例重现空隙

在纯文本编辑器中将以下完整文档保存为 svg-spacing.html,然后在浏览器中打开。它只使用虚构标签和矩形,不依赖任何库或外部文件。浅色矩形标出预期的实例视口;深色矩形表示重复使用的图形。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>SVG spacing example</title>
  <style>
    body {
      margin: 24px;
      font: 16px system-ui, sans-serif;
    }
    #canvas {
      display: block;
      width: 320px;
      height: 200px;
      outline: 1px solid #687580;
      background: #fff;
    }
    .viewport-guide { fill: #e4edf2; }
    .artwork { fill: #356b8c; }
    text { fill: #253744; font-size: 14px; }
  </style>
</head>
<body>
  <p>Two instances with different source bounds and fitting dimensions.</p>
  <svg id="canvas" xmlns="http://www.w3.org/2000/svg"
       width="320" height="200" viewBox="0 0 320 200"
       role="img" aria-labelledby="drawing-title">
    <title id="drawing-title">Padded and tightly fitted rectangles</title>
    <defs>
      <symbol id="padded" viewBox="0 0 100 100">
        <rect id="source-shape" class="artwork"
              x="20" y="20" width="60" height="60" />
      </symbol>
      <symbol id="tight" viewBox="20 20 60 60">
        <rect class="artwork"
              x="20" y="20" width="60" height="60" />
      </symbol>
    </defs>

    <rect class="viewport-guide"
          x="20" y="20" width="120" height="60" />
    <use href="#padded" x="20" y="20" width="120" height="60" />
    <text x="160" y="55">Padded source</text>

    <rect class="viewport-guide"
          x="20" y="110" width="60" height="60" />
    <use id="tight-instance" href="#tight"
         x="20" y="110" width="60" height="60" />
    <text x="110" y="145">Tight source</text>
  </svg>
</body>
</html>

两个实例都明确设置了尺寸。这样在排查空隙时就不必依赖隐式尺寸。MDN 将 use 说明为用于重用所引用的 SVG 内容,并指出当目标是带有 viewBox 的 SVG 或 symbol 时,width 和 height 会影响适配。示例使用当前的 href 属性。MDN:use

计算第一个矩形的实际起点

第一个源图形的 viewBox 为 100×100,但其矩形在两个方向上都只占用坐标 20 到 80。请求的实例视口为 120×60。采用默认的 xMidYMid meet 适配时,缩放比例是 120 / 100 和 60 / 100 中较小的值:0.6。缩放后的 viewBox 占据 60×60,因此水平方向两侧各剩 30 个单位。这符合 W3C 的视口变换算法。SVG 2:计算 SVG 视口的等效变换

对于这些示例输入,绘制出的矩形起点水平坐标为 20 + 30 + 20 × 0.6 = 62,垂直坐标为 20 + 20 × 0.6 = 32。矩形在两个方向上的尺寸均为 60 × 0.6 = 36。这些数值通过一个简单的确定性计算核对得出,并非在浏览器测试中测量的结果。

第二个实例更改了两个独立输入:其源边界变为 20 20 60 60,实例视口变为 60×60。因此矩形会填满该视口,并从 SVG 坐标 (20, 110) 开始。若要自行区分各个原因,每次只更改一个输入。

如何移除实例内部的空白?

修正源边界中意外的空白

viewBox 包含四个数字:最小 x、最小 y、宽度和高度。其原点不必为零。在示例中,viewBox="20 20 60 60" 描述了矩形的现有边界,无需改写其几何形状。MDN:viewBox

收紧实际图形的边界前,先确定其中的内缩是否为有意设计。一组可重复使用的形状可能共用一个坐标框架,以保持其视觉尺寸和位置一致。如果分别收紧每个符号,可能会改变这些关系。如果这种一致性属于设计的一部分,就应保留共用边界。

检查几何图形时,getBBox() 会报告 SVG 用户坐标中的边界。默认情况下,它不包括描边和标记,也不会考虑应用于目标元素或其父元素的变换。因此,仅凭填充部分的边界框不足以判断带描边或经过变换的图形是否会被裁切。MDN:SVGGraphicsElement getBBox()

选择宽高比不匹配时的适配方式

即使图形边界已经收紧,正方形源图形适配到宽视口时,周围仍可能出现空白。请根据预期效果选择策略。将 preserveAspectRatio 应用于被引用的符号,以控制其内部适配;或者在检查外层坐标映射时,将其应用于外层 SVG。MDN:preserveAspectRatio

选择 效果和条件
让源图形与视口比例匹配 保持比例,同时避免宽高比适配产生空白
xMidYMid meet 显示整个 viewBox 并使其居中;比例不匹配时会留下空白
xMinYMin meet 将适配后的 viewBox 移向左上角;剩余空白仍然存在
none 允许非等比缩放;正方形可能会变成矩形
xMidYMid slice 填满视口,但源图形的部分内容会超出视口;可见内容由裁切决定

更改对齐方式只会移动适配产生的空白,无法消除源几何图形中已有的内缩。同样,如果必须让所有源内容都留在视口内,就不适合使用 slice。这些区别符合 MDN 对对齐方式、meet、slice 和非等比适配的定义。MDN:preserveAspectRatio

尺寸不起作用时检查引用目标

普通的组或路径不会仅仅因为被 use 元素引用,就获得用于适配的视口。W3C 指出,如果被引用的元素没有建立新的视口,实例的 width 和 height 就不会生效。若要实现可预测的逐实例适配,请像上面那样将可重复使用的几何图形放入带有明确边界的符号中。若要直接缩放几何图形,请使用变换,并考虑变换对定位的影响。SVG 2:重复使用图形的布局

如何移除外层 SVG 下方的空隙?

SVG 外部的空隙可能由 HTML 行内格式造成。WHATWG 允许在短语内容中使用 SVG,因此周围的页面内容可以与它并排显示。CSS 行框会在基线的上下包含字体度量值,因此行内图形的盒下方可能会留下可见空白。HTML 标准:SVG,CSS 2:行高计算

完整示例已将 display: block 应用于 #canvas,因此该 SVG 不会参与行内行框布局。如果是独立图形,请保留此规则。若要了解其作用机制,可暂时将其改为 display: inline,然后检查周围的行。

如果 SVG 必须与文本并排显示,可以尝试对外层 SVG 设置 vertical-align: bottom,并检查最终的文本对齐方式。该属性会在行框内对齐行内级盒;它不会更改符号边界或内部适配方式。其他内容以及父元素的行高仍可能决定该行的总高度。MDN:vertical-align

请将这些 CSS 声明视为应用于外层 SVG 的不同选项,而不是需要全部粘贴到每个实例上的一组累加修复方法:

/* Standalone drawing */
#canvas {
  display: block;
}

/* Alternative when the drawing must stay inline */
/*
#canvas {
  display: inline;
  vertical-align: bottom;
}
*/

如果坐标来自浏览器视口,该怎么办?

getBoundingClientRect() 会以 CSS 像素报告元素相对于浏览器视口的位置。页面滚动时,该位置会改变。这些值属于与 SVG 用户坐标不同的坐标空间,因此通常不能直接将其 left 和 top 复制到实例的 x 和 y 中。MDN:Element getBoundingClientRect()

如果输入的是浏览器视口中的点,请使用坐标变换。getScreenCTM() 会提供 SVG 变换矩阵;应用其逆矩阵即可将该点转换回所选元素的局部坐标。点的 matrixTransform() 方法会执行此转换。MDN:getScreenCTM(),MDN:matrixTransform()

以下控制台代码片段是对上述完整文档的可选扩展。它会将浏览器视口点 (100, 150) 转换为外层 SVG 的用户坐标,并将边界收紧的实例移到该处。此代码假设示例中的实例未经变换,且仍是该 SVG 的直接子元素。

(() => {
  const canvas = document.getElementById("canvas");
  const instance = document.getElementById("tight-instance");
  if (!canvas || !instance) {
    throw new Error("Open the complete SVG spacing example first.");
  }

  const matrix = canvas.getScreenCTM();
  if (!matrix) {
    throw new Error("The SVG coordinate matrix is unavailable.");
  }

  const viewportPoint = new DOMPoint(100, 150);
  const localPoint = viewportPoint.matrixTransform(matrix.inverse());
  if (!Number.isFinite(localPoint.x) || !Number.isFinite(localPoint.y)) {
    throw new Error("The SVG coordinate matrix cannot be inverted.");
  }

  instance.setAttribute("x", String(localPoint.x));
  instance.setAttribute("y", String(localPoint.y));
})();

有限数值检查很重要,因为不可逆矩阵会产生 NaN 分量。布局或滚动发生变化后,如需转换新的点,请重新计算矩阵。如果实例位于经过变换的组中,请将坐标转换到该父组的坐标系中。研究期间曾根据 API 文档检查此代码片段,但没有在浏览器中运行。MDN:DOMMatrixReadOnly inverse()

最后分别检查三个边界:外层 SVG 盒、符号适配后的视口,以及图形本身的边界。修正引入意外空白的边界,然后在预期的页面尺寸下确认结果。这样就能让定位保持可预测,无需用任意偏移量来补偿原因不明的空隙。

来源