获取 svg 元素的全局变换矩阵

Pas*_*day 3 javascript svg

我想使用浏览器 svg+JavaScript 从用户准备的 svg 模板(例如,在 Inkscape 中创建)生成动态图像,其中有矩形占位符标记应放置动态图形的位置。

理想情况下,用户 - 模板创建者 - 应该被允许以任何他们想要的方式移动、缩放、旋转、倾斜矩形(倾斜后没有那么多的矩形)。他们唯一要做的就是为那些占位符矩形设置正确的 id 值。

我正在寻找一种如何获取/计算这些占位符的变换矩阵的方法。由于它们可以嵌套在组中,因此仅读取元素的属性是不够的。

And*_*ems 5

SVG 命令getCTM和/或getScreenCTM可能是您正在寻找的。(我相信,CTM 代表“当前变换矩阵”。)例如,您可以通过使用 jQuery 检索元素、剥离封闭的 jQuery 对象并调用命令来使用它们,例如$("#mySvgCircleId")[0].getScreenCTM(). 它们都返回 SVG 矩阵对象。两者都会为您提供矩阵信息,这些信息考虑到它们自己的直接转换以及已应用于形状嵌套在其中的任何父 svg 元素的任何转换。这听起来像你要找的。

但是请注意,这两个命令之间存在重要差异。我在下面的代码片段中演示了其中的一些差异。我在那里展示了两个svg元素,一个带有两个红色矩形,一个带有两个蓝色矩形。所有矩形都具有相同的宽度和高度,但每种颜色中的一种是未变换的,而另一种则嵌套在三个不同变换的组中。输出显示了每个矩形使用getCTM和的矩阵结果getScreenCTM。请注意以下事项:

  • 二者getCTMgetScreenCTM考虑到任何封闭祖先元素的帐户变换,例如,任何g组的元素,直到封闭svg元件。例如,对于两个命令,'untransformedRect1' 和'transformedRect1' 返回的矩阵是不同的。
  • getCTM结果不会受到包围的位置svg在其亲代元件元件,而getScreenCTM结果是。例如,'untransformedRect1' 和 'untransformedRect2' 的结果在使用getCTM时相同,但在使用时不同getScreenCTM

例如,如果您正在处理 iframe、在其他 svg 元素中嵌套 svg 元素等,则可能会有进一步的复杂性,我在此不作讨论。

var infoType = "CTM";
show("Matrix Results from getCTM()");
show(getInfo(infoType, "untransformedRect1"));
show(getInfo(infoType, "transformedRect1"));
show(getInfo(infoType, "untransformedRect2"));
show(getInfo(infoType, "transformedRect2"));
show("<br />");

var infoType = "ScreenCTM";
show("Matrix Results from getScreenCTM()");
show(getInfo(infoType, "untransformedRect1"));
show(getInfo(infoType, "transformedRect1"));
show(getInfo(infoType, "untransformedRect2"));
show(getInfo(infoType, "transformedRect2"));

function getInfo(mtrx, id) {
  var mtrx;
  if (infoType === "CTM") {
    var mtrx = $("#" + id)[0].getCTM();
  } else if (infoType === "ScreenCTM") {
    var mtrx = $("#" + id)[0].getScreenCTM();
  }
  var str =
    r(mtrx.a) + ",  " +
    r(mtrx.b) + ",  " +
    r(mtrx.c) + ",  " +
    r(mtrx.d) + ",  " +
    r(mtrx.e) + ",  " +
    r(mtrx.f);
  return id + ": matrix: " + str;
}

function r(num) {
  return Math.round(num * 1000) / 1000;
}

function show(msg) {
  document.write(msg + "<br />");
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Depending on how you are viewing this, you may need to scroll down to see the matrix values.</p>
<div id="containerForSvgs">
  <svg id="svg1" width="150" height="100">
    <rect id="background1" width="300" height="200" fill="#eee" transform="translate(0,0)"></rect>
    <rect id="untransformedRect1" width="20" height="10" fill="red"></rect>
    <g id="group1A" transform="scale(2)">
      <g id="group1B" transform="translate(40,20)">
        <g id="group1C" transform="rotate(-15)">
          <rect id="transformedRect1" width="20" height="10" fill="red"></rect>
        </g>
      </g>
    </g>
  </svg>
  <br />
  shift ===>
  <svg id="svg2" width="150" height="100">
    <rect id="background2" width="300" height="200" fill="#eee" transform="translate(0,0)"></rect>
    <rect id="untransformedRect2" width="20" height="10" fill="blue"></rect>
    <g id="group2A" transform="scale(2)">
      <g id="group2B" transform="translate(40,20)">
        <g id="group2C" transform="rotate(-15)">
          <rect id="transformedRect2" width="20" height="10" fill="blue"></rect>
        </g>
      </g>
    </g>
  </svg>
</div>
<br />
Run Code Online (Sandbox Code Playgroud)

更新实际上,在调查此问题时,我对 SVG 规范进行了更深入的研究,并发现了一个非常酷的其他功能,它可能对您想要的功能更加强大:getTransformToElement. 基本上就可以检索来自任何元件的累积变换矩阵(我将称之为target)到任何其封闭元件(我将称之为enclosing)在一个单一的命令:target.getTransformToElement(enclosing)

我在下面提供了另一个代码片段来演示它的行为,其中 id 的名称希望能清楚地说明它与您的情况的相关性。该片段显示,target.getCTM()基本上提供与target.getTransformToElement(enclosingSvgElement). 然而,此外,它还表明它更灵活,能够显示从子子嵌套元素到其任何祖先封闭元素的变换,任意距离。此外,您可以向任一方向看,例如target.getTransformToElement(enclosing)enclosing.getTransformToElement(target),其中一个将是另一个的矩阵逆(如果我的数学术语在这里是正确的)。

var svg  = $("svg"                                            )[0];
var grp1 = $("#grp1_formatting_of_entire_app"                 )[0];
var grp2 = $("#grp2_menus_and_buttons_and_stuff"              )[0];
var grp3 = $("#grp3_main_drawing_canvas"                      )[0];
var grp4 = $("#grp4_some_intervening_group"                   )[0];
var shp1 = $("#shp1_the_shape_I_currently_care_about"         )[0];
var grp5 = $("#grp5_a_lower_group_I_dont_currently_care_about")[0];

var shp1_CTM     = shp1.getCTM();
var shp1_to_svg  = shp1.getTransformToElement(svg);
var shp1_to_grp3 = shp1.getTransformToElement(grp3);
var grp3_to_shp1 = grp3.getTransformToElement(shp1);

document.write("<table>");

show("getCTM for shp1"                                 , shp1_CTM    );
show("getTransformToElement from shp1 to enclosing svg", shp1_to_svg );
show("getTransformToElement from shp1 to grp3"         , shp1_to_grp3);
show("getTransformToElement from grp3 to shp1"         , grp3_to_shp1);

document.write("</table>");


function show(msg, mtrx) {
  document.write("<tr><td>" + msg + "</td><td>" + mtrxStr(mtrx) + "</td></tr>");
}

function mtrxStr(mtrx) {
  return "( " +
    rnd(mtrx.a) + ", " +
    rnd(mtrx.b) + ", " +
    rnd(mtrx.c) + ", " +
    rnd(mtrx.d) + ", " +
    rnd(mtrx.e) + ", " +
    rnd(mtrx.f) + " )";
}

function rnd(n) {
  return Math.round(n*10)/10;
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg width="200" height="60">
  <g            id="grp1_formatting_of_entire_app"                  transform="translate(30.0, 0)">
    <g          id="grp2_menus_and_buttons_and_stuff"               transform="translate(10.0, 0)">
      <g        id="grp3_main_drawing_canvas"                       transform="translate( 3.0, 0)">
        <g      id="grp4_some_intervening_group"                    transform="translate( 1.0, 0)">
          <rect id="shp1_the_shape_I_currently_care_about"          transform="translate( 0.3, 0)"
                 x="0" y="0" width="100" height="40" fill="red"></rect>
          <g    id="grp5_a_lower_group_I_dont_currently_care_about" transform="translate( 0.1, 0)">
          </g>
        </g>
      </g>
    </g>
  </g>
</svg>
<p>Results</p>
Run Code Online (Sandbox Code Playgroud)

  • 不幸的是,Chrome 不再支持这个很酷的功能:https://www.chromestatus.com/feature/5736166087196672 (4认同)
  • 您可以为不支持该功能的浏览器使用 polyfill(愚蠢的 chrome...) SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(toElement) { return toElement.getScreenCTM().inverse().multiply(this.getScreenCTM()); }; (4认同)