最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Get the global transform matrix of an svg element - Stack Overflow

programmeradmin1浏览0评论

I'd like to use browser svg+JavaScript to generate dynamic images from user-prepared svg templates (say, created in Inkscape) where there are rectangular placeholders that mark the place where the dynamic graphics should be placed.

Ideally, users - the template creators - should be allowed to move, scale, rotate, skew the rectangles (not so much rectangles after skewing) any way they want. The only thing they must do is to set the correct id value for those placeholder rectangles.

I am looking for a way how to get/calculate the transform matrix of those placeholders. Since they can be nested in groups, it is not enough to read the element's properties.

I'd like to use browser svg+JavaScript to generate dynamic images from user-prepared svg templates (say, created in Inkscape) where there are rectangular placeholders that mark the place where the dynamic graphics should be placed.

Ideally, users - the template creators - should be allowed to move, scale, rotate, skew the rectangles (not so much rectangles after skewing) any way they want. The only thing they must do is to set the correct id value for those placeholder rectangles.

I am looking for a way how to get/calculate the transform matrix of those placeholders. Since they can be nested in groups, it is not enough to read the element's properties.

Share Improve this question asked Feb 12, 2016 at 23:20 PassidayPassiday 8,11511 gold badges45 silver badges65 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 6

The SVG mands getCTM and/or getScreenCTM may be what you're looking for. (CTM stands for 'Current Transform Matrix', I believe.) You can use them, for example, by retrieving an element with jQuery, stripping away the enclosing jQuery object and invoking the mand, e.g. $("#mySvgCircleId")[0].getScreenCTM(). They both return SVG matrix objects. Both will give you matrix information that takes into account their own direct transformations as well as any transformations that have been applied to any parental svg elements that the shapes are nested within. That sounds like what you were looking for.

Note, though, that there are important differences between the two mands. I demonstrate some of those differences in the code snippet below. There I show two svg elements, one with two red rectangles and one with two blue ones. All rectangles have the same width and height, but one of each colour is untransformed while the other is nested within three differently transformed groups. The output shows the matrix results using getCTM and getScreenCTM for each rectangle. Note the following:

  • Both getCTM and getScreenCTM take into account transforms of any enclosing ancestor elements, e.g. any g group elements, up to the enclosing svg element. e.g. The returned matrices are different for 'untransformedRect1' and 'transformedRect1' for both mands.
  • The getCTM results are not affected by the location of the enclosing svg element in its parental element, while the getScreenCTM results are. e.g. The results for 'untransformedRect1' and 'untransformedRect2' are identical when using getCTM but are different when using getScreenCTM.

There may be further plications if, for example, you're dealing with iframes, nesting svg elements within other svg elements, etc., which I'm not addressing here.

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 />");
}
<script src="https://ajax.googleapis./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 />

UPDATE Actually, while investigating this I dug a little deeper into the SVG specifications and discovered a very cool other function that might be even more powerful for what you want: getTransformToElement. Basically you can retrieve the cumulative transformation matrix from any element (which I'll call target) to any of its enclosing elements (which I'll call enclosing) in a single mand: target.getTransformToElement(enclosing).

I've provided another code snippet below that demonstrates its behaviour, with id's named that will hopefully make it clear how it could be relevant to your situation. The snippet shows that target.getCTM() essentially provides the same output as target.getTransformToElement(enclosingSvgElement). In addition, however, it also shows that it is more flexible by being able to show the transform from a sub-sub-nested element to any of its ancestral enclosing elements, up any arbitrary distance. Moreover, you can look in either direction, e.g. both target.getTransformToElement(enclosing) and enclosing.getTransformToElement(target), where one will be the matrix inverse of the other (if I'm getting my mathematical terminology correct here).

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;
}
<script src="https://ajax.googleapis./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>

发布评论

评论列表(0)

  1. 暂无评论