【问题标题】:Use SVG animateTransform to rotate a path within a translated SVG within a SVG使用 SVG animateTransform 在 SVG 中旋转已翻译 SVG 中的路径
【发布时间】:2020-12-29 09:50:26
【问题描述】:

这是我之前提出的问题Make HTML Canvas generate PDF-ready Line art? 的后续。在@Peter O. 的建议下,我重新设计了我的软件以使用 SVG 而不是 HTML Canvas。我现在能够创建高质量的图像:

整个对象都有一个外部<svg>。每个视图都有一个翻译后的<svg>。而是将箭头绘制为路径。

这是产生箭头的相关代码:

    transformForPiece(p) {
        return 'translate(' + this.radius + ',' + this.radius + ') rotate('+p.angle+')'
    }
    idForPiece(p) {
        return "arrow-" + p.col + "," + p.row;
    }
    addPathForPiece(p) {
        let x = this.x(p.col);
        let y = this.y(p.row);
        let r = this.radius;
        var box = document.createElementNS('http://www.w3.org/2000/svg','svg');
        box.setAttribute('x', x-r);
        box.setAttribute('y', y-r);
        box.setAttribute('width', r*2);
        box.setAttribute('height', r*2);
        this.svg.append(box)

        /* linexy - draw a line to the X,Y position */
        function lxy(x,y){
            return "L " + x + " " + y + " ";
        }

        let arrow = document.createElementNS('http://www.w3.org/2000/svg','path');
        arrow.setAttribute('style', 'stroke:none;fill:black');
        arrow.setAttribute('transform', this.transformForPiece(p));
        arrow.setAttribute('d',
                          'M 0 0 '+lxy(0,-r) + lxy(-r, 0) + lxy(-r/2,0)
                          + lxy(-r/2, r) + lxy(+r/2,r) + lxy(+r/2,0) + lxy(r,0) + lxy(0,-r) + " Z ");
        arrow.setAttribute('id', this.idForPiece(p));
        p.path = arrow;         // modify the piece with its svg path
        box.append(arrow);
    }

我可以使用 JavaScript 计时器为这些箭头的旋转设置动画:

/* Given a board drawer db, a piece p, a start and and end rotation, rotate it */
const INCREMENT_DEGREES = 10;
const INCREMENT_MS      = 10;
function rotatePieceTimer( db, p, start, end ) {
    if (start > end) {
        start = end;
    }
    p.angle = start;            // set the current angle
    db.drawPiece(p);            // draw the piece at the curren totation
    if (p.angle < end) {         // if we have more to go
        setTimeout( function () { rotatePieceTimer( db, p, start+INCREMENT_DEGREES, end); }, INCREMENT_MS);
    }
}

这与事件相关:

    function roll (e) {
        /* Pick a random piece */
        let i = Math.floor(Math.random() * cells_wide);
        let j = Math.floor(Math.random() * cells_high);
        let p = board.piece(i,j); // get the piece
        console.log("roll p=",p);
        rotatePieceTimer(db, p, p.angle, Math.ceil(p.angle/180)*180+180) // rotate with timer
        //rotatePieceSVG(db, p, p.angle, Math.ceil(p.angle/180)*180+180) // rotate with timer
        //$('#time').text(boardHistory.length);
    }

    $('#roll').click(  roll );

从注释掉的代码中可以看出,我想使用 SVG 动画转换来制作动画。我所拥有的一切都很好,但是知道如何使动画变换工作会很好。

我已经将工作代码抽象到这里:

<!DOCTYPE html>
<html lang="en" class="stylish" type="text/css>"
      <body>
        <svg width="400" height="400">
          <svg x="203" y="203" width="60.66666666666667" height="60.66666666666667">
            <path style="stroke:none;fill:black" transform="translate(30.333333333333336,30.333333333333336) rotate(45)"
                  d="M 0 0 L 0 -30.333333333333336 L -30.333333333333336 0 L -15.166666666666668 0 L -15.166666666666668 30.333333333333336 L 15.166666666666668
                     30.333333333333336 L 15.166666666666668 0 L 30.333333333333336 0 L 0 -30.333333333333336  Z "
                  id="arrow-3,3">
              <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="180 30.333333333333336 30.333333333333336"
                                to="360 30.333333333333336 30.333333333333336" dur="1s" repeatCount="100">
              </animateTransform>
            </path>
    </svg>
    </svg>
      </body>
</html>

使用animateTransform 的问题在于箭头不能很好地旋转,就像我在path 中简单地更新rotate(45) CSS 变换时那样。

我遇到的另一个问题是我想在用户单击按钮时实现这一点,有点像这段代码(已损坏):

function rotatePieceSVG( db, p, start, end){
    // this just changes the attribute:

    var noAnimation = false;
    if (noAnimation) {
        p.angle = end;
        p.path.setAttribute('transform',db.transformForPiece(p));
        return;
    }

    // This is the animation I can't get to work...
    var t = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
    t.setAttribute('attributeName','transform');
    t.setAttribute('attributeType','XML');
    t.setAttribute('type','rotate');
    t.setAttribute('from',p.angle+' '+db.radius+' '+db.radius);
    p.angle = end;
    t.setAttribute('to',p.angle+' '+db.radius+' '+db.radius);
    t.setAttribute('dur','1s');
    p.path.append(t);
    console.log("after rotate: p=",p);
}

我已验证此代码将animateTransform 添加到 DOM 的正确位置。但是,似乎将animateTransform 添加到现有路径对象不会导致路径被动画化。我已经阅读了规范,但它很大,我可能错过了一些东西。我是否需要删除旧路径对象并添加一个新对象,而不仅仅是添加动画变换?大概。我还没试过。

所以我的问题:

  1. 如何让animateTransform 正确旋转我的箭头?我试过摆弄from=to= 中的值,但我永远无法让箭头在中心旋转。

  2. 有没有办法将animateTransform 添加到现有路径中,还是我需要删除当前路径并添加新路径?

完整代码在这里:https://jsfiddle.net/simsong/9udcga6r/1/

【问题讨论】:

    标签: javascript svg css-animations svg-animate


    【解决方案1】:

    您的代码中突出的是您的&lt;animateTransform&gt; 中缺少begin 的值。 begin 值非常通用。

    • 您可以将它们设置为"0s",这是默认值,并且与文档时间线开始的时间一致(基本上是窗口load 事件)。现在不是您以后以交互方式添加标签的时候。那个时候,0s就会过去了。

    • 您可以将它们设置为"indefinite",这样动画就不会开始。这通常与基于脚本的触发器结合使用,例如SVGAnimationElement.beginElement()

    • 您可以将它们设置为任意事件。 "myButton.click" 将在带有id="myButton" 的元素收到点击时启动动画。

    该语法还提供了开始时间列表和延迟。非常值得研究 spec 中的语法以了解您可以做什么。 (时间值规范中唯一没有被浏览器实现的部分是wallclock() 函数。)

    对于旋转中心,您需要仔细查看您变换的内容以及旋转的内容。就您的代码而言,&lt;animateTransform&gt; 替换元素上的transform 属性全部。您可能想要的是添加到现有转换列表末尾的 补充 转换。这是由additive="sum" 属性控制的(默认为"replace")。

            <path style="stroke:none;fill:black" transform="translate(30.333,30.333) rotate(45)"
                  d="M 0 0 L 0 -30.333 L -30.333 0 L -15.166 0 L -15.166 30.333 L 15.166
                     30.333 L 15.166 0 L 30.333 0 L 0 -30.333 Z "
                  id="arrow-3,3">
              <animateTransform attributeName="transform" type="rotate"
                                from="0"
                                to="180"
                                begin="myButton.click" dur="1s" repeatCount="100"
                                additive="sum">
              </animateTransform>
            </path>
    

    这将围绕本地用户空间坐标中的(0, 0) 点旋转元素,从0 到180 度连续100 次。需要仔细研究的三件事:

    1. 在应用所有先前的变换之后,必须在本地用户空间坐标中设置旋转中心,因为它被添加到变换列表的末尾。 (坐标系的变换,而不是绘制到其中的对象。)这相当于:它与路径数据使用的坐标系相同。该路径的中心位于坐标系原点。

    2. 变换添加到底层元素变换。这意味着,它不会取代最初的 45 度旋转,而是增加它。因此,使用恒等变换开始加性变换通常是一个好主意。

    3. 按照您的定义,旋转从 0 度变为 180 度,然后立即重新开始。这意味着它将立即跳回其初始位置。如果你想要一个平滑的运动,要么从 0 度旋转到 360 度,要么设置属性accumulate="sum",这将在上一个迭代结束时开始下一次迭代。

    【讨论】:

    • 感谢您提供详细信息。我会试一试并报告。
    猜你喜欢
    • 2014-02-19
    • 2016-01-16
    • 2021-03-27
    • 2015-02-26
    • 2019-04-15
    • 2020-08-10
    • 1970-01-01
    • 2013-04-16
    • 2020-04-24
    相关资源
    最近更新 更多