【问题标题】:Is it possible to escape an SVG viewbox? (or how to position HTML DIV over scaled/transformed SVG?)是否可以转义 SVG 视图框? (或如何将 HTML DIV 放置在缩放/转换的 SVG 上?)
【发布时间】:2014-11-24 17:46:18
【问题描述】:

在本文档中:

html, body {
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden
}
#content {
  width: 100%;
  height: 100%
}
<div id="content">
  <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="90%" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid meet">
    <rect style="fill:none;stroke:black" width="540" height="157" x="690" y="26"></rect>
    <text style="font-size:148px;text-align:center;text-anchor:middle;fill:black;stroke:none" x="960" y="145">Test</text>
    <text style="font-size:112px;text-align:center;text-anchor:middle;fill:black;stroke:none" x="960" y="340">Lorem ipsum etc etc</text>
    <foreignObject x="10" y="726" width="1901" height="347">
      <div xmlns="http://www.w3.org/1999/xhtml" style="width:99.6%;height:97.7%;border:4px solid blue">
        <p style="text-align:center">Hello World, from HTML inside SVG.</p>
      </div>
    </foreignObject>
  </svg>
  <div style="width:99%;border:4px solid blue">
    <p style="text-align:center">Hello World, from HTML outside SVG.</p>
  </div>
</div>

我正在使用 viewBox 来重新缩放任意大小的 SVG,以使其填充浏览器窗口(通常 svg 上 90% 的高度为 100%;为了便于说明,我在此处对其进行了缩小)。在该 SVG 中有一个 foreignObject,其中包含一些要呈现的 HTML。

就目前而言,渲染的 HTML 输出也被重新缩放以适应 viewBox 坐标;注意 inside-svg 内容的边框宽度和文本大小与外部不同,并且在调整窗口大小时会发生变化。

我希望 foreignObject 的位置和大小来定义内部 div 的边界框,但我不希望它实际重新缩放内容,只是为了重排它们。有没有办法做到这一点?

(顶部的文本也会随着窗口重新缩放,但在这种情况下这是需要的。)

请注意,我无法移动或移除 viewBox。我可以将内部 div 放在 svg 之外并使用 JavaScript 来调整它的大小,但我不知道如何将它的边界框(但不是缩放)设置为如果它在里面的话.

(我不明白的一个不相关的事情是我必须将 div 的宽度/高度指定为小于 100%,否则它会裁剪内部和外部的边框。虽然这可能只是 Chrome 的事情并不重要;我只是好奇。)


编辑AmeliaBR's answer之后,这是我想出的代码:

function svgTransform(x, y, matrix, svg) {
  var p = svg.createSVGPoint();
  p.x = x;
  p.y = y;
  return p.matrixTransform(matrix);
}

function svgScreenBounds(svgElement) {
  var matrix = svgElement.getScreenCTM();
  var r = svgElement.getBBox();
  var leftTop = svgTransform(r.x, r.y, matrix, svgElement.ownerSVGElement);
  var rightBottom = svgTransform(r.x + r.width, r.y + r.height, matrix, svgElement.ownerSVGElement);
  return {
    x: leftTop.x,
    y: leftTop.y,
    width: rightBottom.x - leftTop.x,
    height: rightBottom.y - leftTop.y
  };
}

function adjustOverlay() {
  var placeholder = document.getElementById('placeholder');
  var overlay = document.getElementById('overlay');
  var bounds = svgScreenBounds(placeholder);
  overlay.style.left = bounds.x + 'px';
  overlay.style.top = bounds.y + 'px';
  overlay.style.width = bounds.width + 'px';
  overlay.style.height = bounds.height + 'px';
  overlay.style.display = 'block';
}
html,body { height: 100%; margin: 0; padding: 0; overflow: hidden }
#content { width: 100%; height: 100% }
#overlay {
  display: none;
  position: absolute;
  border:4px solid blue;
  box-sizing: border-box;
}
<body onload="adjustOverlay()" onresize="adjustOverlay()">
  <div id="content">
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="0 0 1920 1080" preserveAspectRatio="xMidYMid meet">
      <rect style="fill:none;stroke:black" width="540" height="157" x="690" y="26"></rect>
      <text style="font-size:148px;text-align:center;text-anchor:middle;fill:black;stroke:none" x="960" y="145">Test</text>
      <text style="font-size:112px;text-align:center;text-anchor:middle;fill:black;stroke:none" x="960" y="340">Lorem ipsum etc etc</text>
      <rect style="fill:yellow;stroke:black" width="1901" height="100" x="10" y="618" rx="20" ry="20"></rect>
      <text style="font-size:64px;text-align:center;text-anchor:middle;fill:black;stroke:none" x="960" y="685">Bottom banner text</text>
      <rect style="visibility:hidden" x="10" y="726" width="1901" height="347" id="placeholder"></rect>
    </svg>
  </div>
  <div id="overlay">
    <p style="text-align:center">Hello World, from HTML outside SVG.</p>
  </div>
</body>

它的行为似乎符合预期。 (宽度小于 100% 的原因是因为我需要 box-sizing: border-box - 在提出问题之前我已经尝试过 html 元素的样式,但它在那里没有效果;显然它需要直接应用而不是继承。)

【问题讨论】:

  • viewBox 不是一个窗口,它不是你“移动”或“删除”或“改变或“覆盖”的东西。它是一种坐标系,对于 SVG 元素来说是固定的和绝对的.
  • viewBox 属性 是可以移动或删除的东西,并且可能是可能答案的一部分,除了我不能在不破坏其他东西的情况下做到这一点我想按照他们的方式工作。这就是我这么说的原因。
  • 感谢您添加详细代码以帮助他人。也很好地组织成逻辑函数。
  • 我可能应该补充一点,如果以后有人遇到这个并想使用它,我忽略了当前页面滚动位置的调整,因为我知道在我的情况下页面无法滚动。如果需要,请查看AmeliaBR's CodePen;那里有一个例子(搜索window.pageXOffset)。添加起来应该很简单,但我不想把它放在自己身上,因为我不打算测试它。

标签: html svg viewbox


【解决方案1】:

很遗憾,您的问题的答案是“不”; viewBox 会更改其所有子级的整个坐标系,包括 px 或 pt 单位的定义。

&lt;foreignObject&gt; 中的内容被渲染到一个屏幕缓冲区(临时图像),其宽度和高度与 foreignObject 在本地 SVG 坐标系中的宽度和高度相同,然后将结果转换为适合屏幕,就像您在转换后的 SVG 坐标系中绘制(JPEG 或 PNG)图像一样。

如果使用 Javascript 是一种选择,具有可读文本和响应式图形的最佳解决方案是将绝对定位的 &lt;div&gt; 元素叠加在 SVG 上。 This CodePen I put together about tooltips 应该可以帮助您了解如何在 SVG 坐标系和页面坐标系之间进行转换。特别是,您需要阅读 SVGPoint 对象和 getScreenCTM() 函数。

虽然工作量更大,但这种方法还可以避免foreignObject 的跨浏览器问题,IE 完全不支持这些问题,并且在其他浏览器中相当不稳定(例如,并不总是获得您需要的滚动条)。

【讨论】:

  • getScreenCTM 要求已将 SVG 添加到“真实”文档模型中。虽然我可以做到这一点,但目前我正在尝试对 SVG 模型在树外进行任何基于脚本的更改,以便在我最终将其添加到树而不是更新时让所有内容立即出现零碎的。那么有没有类似于getScreenCTM 的东西可以在树外工作?
  • 没有类似的东西会影响视图框——您可以使用常规 getCTM 方法计算父 SVG 和任何元素之间的净变换,但您需要计算视图框缩放转换为父 HTML 坐标系。不过,我不确定您是否需要过多担心多个布局任务的重绘性能——您仍然可以批量添加 SVG,然后绝对定位的 div 不会创建任何布局重新计算。
  • 请记住,当您的窗口(以及您的响应式 SVG)调整大小时,您需要重新调用设置 div 的位置和大小的 Javascript 函数,因此您不会将能够预先计算一切。
  • 谢谢;根据您之前的回答,我修改了示例代码以使用getScreenCTM,现在它的行为似乎符合预期。听起来没有它可能会做太多的工作。现在我只需要确保它在应用于我的真实页面时仍然有效,而不仅仅是一个玩具示例。 :)
【解决方案2】:

另一种解决方案是将下部 DIV(SVG 外部)放置在 SVG 区域上方。

&lt;div style="width:99%;border:4px solid blue; position:relative;top:-100px;"&gt;

foreignObject 在 SVG 中的缩放比例是正确的。当您可以简化它时,在 HTML 中的 SVG 中包含 DIV 仍然没有意义(恕我直言的复杂编码方式)。或者,如果您想控制 SVG 中的文本大小,请使用 &lt;text...&gt; 标签。

【讨论】:

  • 这是我在我的问题中提出的替代方案,但是只是说这是没用的,不知道如何计算位置应该是什么。它不能是静态 CSS,它必须通过脚本从 SVG 内容以某种方式计算出来。
猜你喜欢
  • 2013-06-20
  • 2014-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-10
相关资源
最近更新 更多