【问题标题】:Get the global transform matrix of an svg element获取 svg 元素的全局变换矩阵
【发布时间】:2016-02-12 23:20:29
【问题描述】:

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

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

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

【问题讨论】:

    标签: javascript svg


    【解决方案1】:

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

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

    • 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 />");
    }
    <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 />

    更新实际上,在对此进行调查时,我对 SVG 规范进行了更深入的研究,并发现了一个非常酷的其他功能,它可能更强大,可以满足您的需求:getTransformToElement。基本上,您可以在一个命令中从任何元素(我将其称为target)检索到其任何封闭元素(我将其称为enclosing)的累积变换矩阵:target.getTransformToElement(enclosing)

    我在下面提供了另一个代码 sn-p 来演示它的行为,并用 id 命名,希望能清楚地说明它与您的情况的相关性。 sn-p 显示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;
    }
    <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>

    【讨论】:

    • 很遗憾,Chrome 不再支持这个很酷的功能了:chromestatus.com/feature/5736166087196672
    • 这太糟糕了,因为我很高兴找到这个。这将使在大量分组的 svg 图像中的操作更加容易。那好吧。非常感谢您提供的信息。
    • 您可以为不支持该功能的浏览器使用 polyfill(愚蠢的 chrome...) SVGElement.prototype.getTransformToElement = SVGElement.prototype.getTransformToElement || function(toElement) { return toElement.getScreenCTM().inverse().multiply(this.getScreenCTM()); };
    • @JasonCrist,这是一个很棒的 polyfill,谢谢!不过我很担心。 Polyfills 通常用于尚未由特定浏览器实现的功能。这是一个已经实现然后被主动删除的功能。这是否代表了从所有浏览器中删除此功能的更广泛趋势的一部分的幕后对话?如果是这样,那么在某些时候,这样的 polyfill 可能是不值得战斗的艰苦战斗。但是,如果至少有 getScreenCTM 留在这里,那么您的解决方案绝对是有价值的。
    猜你喜欢
    • 1970-01-01
    • 2016-04-05
    • 1970-01-01
    • 2019-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多