简体   繁体   中英

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:

<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.

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).

There is probably a solution using createSVGPoint() and getScreenCTM() or getCTM() but frankly I'm not sure what I should be using.

An SVG without a viewBox attribute has a width of 300px and a height of 150px. I've added a 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. 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()

In order to center the wrap I need to know the center of the main svg canvas: x = 300/2; y=150/2. 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() . 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> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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