使用非比例视图框时,将一个SVG元素放在另一个上面

时间:2016-01-27 20:53:56

标签: javascript svg

我有这个SVG:

<svg id="root" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="347.27mm" viewBox="0 0 409.32783 1230.3321" width="115.48mm" version="1.1">
  <circle id="b1" cx="0" cy="0" r="15" class="bottom" />
  <circle id="b2" cx="120" cy="100" r="15" class="bottom" />
  <g transform="translate(17,43)">
    <circle id="b3" cx="240" cy="200" r="15" class="bottom" />
    <circle id="b4" cx="360" cy="300" r="15" class="bottom" />
  </g>

  <!--      <g transform="translate(10,20)"> -->
  <circle id="t1" cx="80" cy="30" r="15" class="top" />
  <circle id="t2" cx="130" cy="30" r="15" class="top" />
  <!--        <g transform="translate(10,20)"> -->
  <circle id="t3" cx="180" cy="30" r="15" class="top" />
  <circle id="t4" cx="230" cy="30" r="15" class="top" />
  <!--    </g> -->
  <!--      </g> -->
</svg>

红球坐标被后面的代码更改后。它呈现为:

Rendered SVG

我想把红球放在绿色的上面。我有一个解决方案,如果您删除SVG标记中的viewBox属性。但不幸的是我无法做到这一点。但是,如果viewBox不存在,则此方法有效:

var svg = document.getElementById("root");

for (var index = 1; index <= 4; index++) {
  var bottom = svg.getElementById("b" + index);
  var top = svg.getElementById("t" + index);

  var targetX = parseFloat(top.getAttribute("cx"));
  var targetY = parseFloat(top.getAttribute("cy"));

  var center = getElementCenter(svg, bottom);

  top.setAttribute("cx", parseFloat(center.x));
  top.setAttribute("cy", parseFloat(center.y));
}

function getTransformedCoords(x, y, matrix) {
  var transformedX = x * matrix.a + y * matrix.c + matrix.e;
  var transformedY = x * matrix.b + y * matrix.d + matrix.f;

  return {
    x: transformedX,
    y: transformedY
  };
}

function getElementCenter(svg, element) {
  var x, y, width = 0, height = 0;

  var boundingBox = element.getBBox();
  var centerX = boundingBox.x + (boundingBox.width / 2);
  var centerY = boundingBox.y + (boundingBox.height / 2);

  var out = getTransformedCoords(centerX, centerY, element.getCTM()); // getCTM affected by the viewport

  centerX = out.x;
  centerY = out.y;

  return {
    x: centerX,
    y: centerY
  };
}

我的工作理论是,这是因为viewBox与视口的比例不同。但我不知道如何解决这个问题。

我想使用普通JavaScript 来执行此操作。即使使用合适的库也不错。

问题:

  1. 如何在保持viewBox的同时使其工作?
  2. 是否可以让它解释绿色圆圈的转换?我在SVG中对这些内容进行了评论,但是当它们在那里时,有一些可行的东西会很好。但是数字1足以让我接受答案。
  3. JSFiddle如果你想尝试代码:https://jsfiddle.net/kentl/gpqcdecx/

    PS。如果要设置样式,这是CSS:

    .top {fill:red;animation:fall 4s ease-in-out infinite;}
    .bottom{fill:green;}
    @keyframes fall {0%{opacity:0.0;}50%{opacity:1.0;}100%{opacity:0.0;}}
    

1 个答案:

答案 0 :(得分:0)

只需使用getTransformToElement。对于Chrome,您需要一个polyfill,因为它不再在那里实施。

&#13;
&#13;
// The goal is to place the red circles on top of the green ones

// Working theory:
//   The viewBox hasn't got the same ratio as the viewport, which affects the returned transformation, returning
//   a matrix with slightly larger numbers than 1 for scaling X and Y. As that matrix is used to convert from the
//   local coordinate space in the bottom element, into the global one used by the top element, the coordinates are
//   scaled up slightly.. But when those coordinates are set in the top element, they'll be affected AGAIN by the
//   same up-scaling. Making them become more and more off the further from 0,0 we come.

// Question:
// 1. How can that be fixed?
// 2. Only if it's not too hard: Uncomment the <g>'s in the SVG. How do we compensate for the affected top CTM as well?


console.clear();

var svg = document.getElementById("root");

for (var index = 1; index <= 4; index++) {
  var bottom = document.getElementById("b" + index);
  var top1 = document.getElementById("t" + index);

  var targetX = parseFloat(top1.getAttribute("cx"));
  var targetY = parseFloat(top1.getAttribute("cy"));

  var center = getElementCenter(svg, bottom);
  
  t = bottom.getTransformToElement(top1);

  top1.setAttribute("cx", parseFloat(center.x) + t.e);
  top1.setAttribute("cy", parseFloat(center.y) + t.f);
}

function getElementCenter(svg, element) {
  var x, y, width = 0, height = 0;

  var boundingBox = element.getBBox();
  var centerX = boundingBox.x + (boundingBox.width / 2);
  var centerY = boundingBox.y + (boundingBox.height / 2);

  return {
    x: centerX,
    y: centerY
  };
}
&#13;
.top {
  fill: red;
  animation: fall 4s ease-in-out infinite;
}

.bottom {
  fill: green;
}

@keyframes fall {
  0% {
    opacity: 0.0;
  }
  50% {
    opacity: 1.0;
  }
  100% {
    opacity: 0.0;
  }
}
&#13;
<svg id="root" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="347.27mm" viewBox="0 0 409.32783 1230.3321" width="115.48mm" version="1.1">
  <circle id="b1" cx="0" cy="0" r="15" class="bottom" />
  <circle id="b2" cx="120" cy="100" r="15" class="bottom" />
  <g transform="translate(17,43)">
    <circle id="b3" cx="240" cy="200" r="15" class="bottom" />
    <circle id="b4" cx="360" cy="300" r="15" class="bottom" />
  </g>

  <!--      <g transform="translate(10,20)"> -->
  <circle id="t1" cx="80" cy="30" r="15" class="top" />
  <circle id="t2" cx="130" cy="30" r="15" class="top" />
  <!--        <g transform="translate(10,20)"> -->
  <circle id="t3" cx="180" cy="30" r="15" class="top" />
  <circle id="t4" cx="230" cy="30" r="15" class="top" />
  <!--    </g> -->
  <!--      </g> -->
</svg>
&#13;
&#13;
&#13;