简体   繁体   English

如何获得内部SVG元素相对于外部SVG元素视口的位置?

[英]How do I get an internal SVG element's position relative to the viewport of an outer SVG element?

Say I have an SVG element containing some stuff: 假设我有一个包含一些东西的SVG元素:

<div style="margin-left:50px; width: 100%; min-height: 400px;">
  <svg>
    <g transform="translate(34.34,47.5) scale(0.345)" height="100%" width="100%">
      <svg x="20" y ="50" style="overflow: visible">
        <circle cx="0" cy="0" r="35" stroke="red" fill="blue">
        <text>a bunch of text</text>
      </svg>
      <line />
    </g>
  <svg>
<div>

I'm trying to find the center position of the <g> relative to the viewport of the outer <svg> element, so that I can translate the <g> to be centered within the outer <svg> , and scale it to fit. 我试图找到<g>相对于外部<svg>元素的视口的中心位置,这样我就可以将<g>转换为在外部<svg>居中,并将其缩放以适合。

I was able to get it working using getBoundingClientRect() and adjusting for the transform scale, but this does not work in Firefox because the <svg> elements inside the <g> container are not constrained to the bounding box of the displayed section of their contents (rather it's the same size as the outer <svg> , with some scaling). 我能够使用getBoundingClientRect()并调整变换比例,但这在Firefox中不起作用,因为<g>容器内的<g> <svg>元素不受约束到其显示部分的边界框内容(而不是外部<svg>大小,有一些缩放)。

There is probably a solution using createSVGPoint() and getScreenCTM() or getCTM() but frankly I'm not sure what I should be using. 可能有一个使用createSVGPoint()getScreenCTM()getCTM()的解决方案,但坦率地说,我不确定我应该使用什么。

An SVG without a viewBox attribute has a width of 300px and a height of 150px. 没有viewBox属性的SVG的宽度为300px,高度为150px。 I've added a viewBox="0 0 300 150" . 我添加了一个viewBox="0 0 300 150" You can remove it. 你可以删除它。

Also I've added a rectangle to be able to see the position and the size of the <g> element. 我还添加了一个矩形,以便能够看到<g>元素的位置和大小。 You can remove it as well. 你也可以删除它。

How I would center the <g> element : Since the <g> element is transformed the easiest way to get it's size and position would be wrapping the <g> element in another one, in this case <g id="wrap"> Next I can get the bounding box of the wrap: wrap.getBBox() 我如何使<g>元素居中 :由于<g>元素被转换,获得它的大小和位置的最简单方法是将<g>元素包装在另一个元素中,在这种情况下<g id="wrap">接下来我可以获得wrap的边界框: wrap.getBBox()

In order to center the wrap I need to know the center of the main svg canvas: x = 300/2; 为了使包裹居中,我需要知道主svg画布的中心:x = 300/2; y=150/2. Y =二分之一百五十。 Now I can translate the wrap into the center 现在我可以将包裹翻译成中心

 let c = {x:150,y:75}//the center of the main svg element let bb = wrap.getBBox()//the bounding box of the wrap let transformation = `translate(${cx - bb.x - bb.width/2}, ${cy - bb.y - bb.height/2})` wrap.setAttributeNS(null,"transform",transformation) 
 svg{border:1px solid;width:100vh;} text{fill:black;} path{fill:none;stroke:black} 
 <div style="margin-left:50px; width: 100%; min-height: 400px;"> <svg id="main" viewBox="0 0 300 150" > <g id="wrap"> <rect x="29.165" y="47.5" width="45.03" height="29.325" fill="gold" fill-opacity=".5" /> <g transform="translate(34.34,47.5) scale(0.345)" height="100%" width="100%"> <svg x="20" y ="50" style="overflow: visible"> <circle cx="0" cy="0" r="35" stroke="red" fill="blue"/> <text>a bunch of text</text> </svg> <line /> </g> </g> <path d="M0,0L300,150M0,150L300,0" /> <svg> <div> 

I hope this is what you were asking. 我希望这就是你所要求的。

I managed to figure out a solution using one of the d3.zoom transform methods (we're using d3.zoom to manage the translate/scale transform), and SVGElement.getBBox() . 我设法使用d3.zoom转换方法(我们使用d3.zoom管理转换/缩放转换)和SVGElement.getBBox()来找出解决方案。 I originally was using this method but had messed up the calculation; 我最初使用的是这种方法,但搞砸了计算; this way it works though. 这种方式虽然有效。

 const selection = d3.select(group); const zoomBehavior = d3.zoom().on('zoom', () => { selectionTransform = d3.event.transform; }); selection.call(zoomBehavior); const scaleAndTransformTo = () => { selection.call(zoomBehavior.translateBy, Math.random() * 100, Math.random() * 150); group.setAttribute("transform", selectionTransform.toString()); } scaleAndTransformTo(); reset.addEventListener('click', scaleAndTransformTo); run.addEventListener('click', () => { const { width: containerWidth, height: containerHeight } = container.getBoundingClientRect(); const containerCenter = [containerWidth / 2, containerHeight / 2]; const { height, width, x, y } = group.getBBox(); const nodeBBoxCenter = [x + (width / 2), y + (height / 2)]; // Apply the current interpolated translate/scale to the BBox center to get the actual position const groupCenterCoords = selectionTransform.apply(nodeBBoxCenter); const translationOffset = [ (containerCenter[0] - groupCenterCoords[0]) / selectionTransform.k, (containerCenter[1] - groupCenterCoords[1]) / selectionTransform.k, ]; selection.call(zoomBehavior.translateBy, ...translationOffset); group.setAttribute("transform", selectionTransform.toString()); }); 
 #page { display: flex; flex-direction: column; position: relative; align-items: stretch; margin-left: 100px; } #container { background-color: grey; flex-grow: 1; flex-shrink: 0; min-height: 500px; border: 1px solid red; } #group > svg { overflow: visible; } #group > svg > circle { overflow: visible; } text { fill: black; } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> <div id="page"> <div> <button id="run">Run</button> <button id="reset">Reset</button> </div> <svg id="container"> <gx="0" y="0" id="group" width="100%" height="100%"> <line x1="20" y1="50" x2="150" y2="150" stroke="brown" /> <svg x="20" y ="50"> <circle cx="0" cy="0" r="35" stroke="red" fill="blue"> <text x="35" y="0" height="100%" width="100%">a bunch of text</text> </svg> <line x1="100" y1="350" x2="160" y2="340" stroke="brown" /> <svg x="100" y ="350"> <circle cx="0" cy="0" r="35" stroke="red" fill="blue"> <text x="35" y="0" height="100%" width="100%">a bunch of text 3</text> </svg> </g> <svg> <div> 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM