使用SVG animateTransform旋转SVG中已翻译SVG内的路径 [英] Use SVG animateTransform to rotate a path within a translated SVG within a SVG

查看:84
本文介绍了使用SVG animateTransform旋转SVG中已翻译SVG内的路径的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是我之前问的一个问题的后续内容,

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

以下是产生箭头的相关代码:

  transformForPiece(p){返回'translate('+ this.radius +','+ this.radius +')rotation('+ p.angle +')'}idForPiece(p){返回箭头"+ p.col +,"+ p.row;}addPathForPiece(p){令x = this.x(p.col);令y = this.y(p.row);令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-在X,Y位置画一条线*/函数lxy(x,y){返回"L"+ x +""+ y +"" ;;}让箭头= 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 =箭头;//用svg路径修改片段box.append(箭头);} 

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

 /*给定板式抽屉db,件p,开始和结束旋转,将其旋转*/const INCREMENT_DEGREES = 10;const INCREMENT_MS = 10;函数RotatePieceTimer(db,p,start,end){如果(开始>结束){开始=结束;}p.angle =开始;//设置当前角度db.drawPiece(p);//以curren的方式绘制作品if(p.angle< end){//如果还有更多的事情要做setTimeout(function(){rotationPieceTimer(db,p,start + INCREMENT_DEGREES,end);},INCREMENT_MS);}} 

与事件有关:

 功能卷(e){/*随机选择一块*/让我= Math.floor(Math.random()* cells_wide);令j = Math.floor(Math.random()* cells_high);令p = board.piece(i,j);//拿到一块console.log("roll p =",p);rotationPieceTimer(db,p,p.angle,Math.ceil(p.angle/180)* 180 + 180)//使用计时器旋转//rotatePieceSVG(db,p,p.angle,Math.ceil(p.angle/180)* 180 + 180)//旋转计时器//$('#time').text(boardHistory.length);}$('#roll').click(roll); 

从注释掉的代码中可以看出,我想使用SVG animationTransform进行动画处理.我所拥有的一切都很好,但是知道如何使动画变形"工作变得很整洁.

我已经将工作代码提取到这里:

 <!DOCTYPE html>< html lang ="en";class ="stylish"type ="text/css>";<身体>< svg width ="400"高度="400".< svg x ="203";y ="203".宽度="60.66666666666667"height ="60.66666666666667".< path style ="stroke:none; fill:black"transform =翻译(30.333333333333336,30.333333333333336)旋转(45)"d ="M 0 0 L 0 -30.333333333333336 L -30.333333333333336 0 L -15.166666666666668 0 L -15.166666666666668 30.333333333333336 L 15.16666666666666830.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"来自="180 30.333333333333336 30.333333333333336"改为"360 30.333333333333336 30.333333333333336"dur ="1s"repeatCount ="100"></animateTransform></path></svg></svg></body></html> 

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

我遇到的另一个问题是,我想在用户单击按钮时使这种情况发生,其中包含以下代码(已损坏):

 函数rotatePieceSVG(db,p,开始,结束){//这只是更改了属性:var noAnimation = false;如果(noAnimation){p.angle =结束;p.path.setAttribute('transform',db.transformForPiece(p));返回;}//这是我无法播放的动画...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 =结束;t.setAttribute('to',p.angle +''+ db.radius +''+ db.radius);t.setAttribute('dur','1s');p.path.append(t);console.log(旋转后:p =",p);} 

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

所以我的问题:

  1. 如何获取 animateTransform 来正确旋转我的箭头?我尝试摆弄 from = to = 中的值,但我永远无法使箭头在中心旋转.

  2. 是否可以将 animateTransform 添加到现有路径,还是需要删除当前路径并添加新路径?

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

解决方案

代码中突出的是< animateTransform> 中缺少 begin 的值>.开始值用途广泛.

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

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

  • 您可以将它们设置为任意事件.当 id ="myButton" 的元素获得点击时,"myButton.click" 将开始播放动画.

该语法还提供了开始时间列表和延迟.值得研究 spec 中的语法以找出可以做什么.(时间值规范中唯一未由浏览器实现的部分是 wallclock()函数.)

对于旋转中心,您需要仔细查看变换的内容和旋转的内容.按照您的代码立场,< animateTransform> 完全替换元素上的 元素上的 transform 属性.您可能想要的是将补充转换添加到现有转换列表的末尾.这由属性 additive ="sum" 进行控制(默认为替换" ).

 < path style ="stroke:none; fill:black"transform ="translate(30.333,30.333)rotation(45)"d ="M 0 0 L 0 -30.333 L -30.333 0 L -15.166 0 L -15.166 30.333 L 15.16630.333 L 15.166 0 L 30.333 0 L 0 -30.333 Zid ="arrow-3,3"< animateTransform attributeName ="transform";type ="rotate"来自="0"等于"180"begin ="myButton.click"dur ="1s"repeatCount ="100"添加剂=和".</animateTransform></path> 

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

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

  2. 转换添加到基础元素转换.这意味着,它不会代替初始的45度旋转,而是会增加它.因此,从身份变换开始加法变换通常是一个好主意.

  3. 按照您的定义,旋转从0到180度,然后立即重新启动.这意味着它将立即跳回其初始位置.如果要平滑移动,请从0到360度旋转,或设置属性 accumulate="sum" ,它将在上一个迭代的末尾开始下一个迭代.

This is a follow-on to a previous question I asked, Make HTML Canvas generate PDF-ready Line art?. At @Peter O.'s suggestion, I reworked my software to use SVG instead of HTML Canvas. I am now abler to create a high-quality image:

There is an outer <svg> for the entire object. There is a translated <svg> for each view. Instead that the arrow is drawn as a path.

Here is the relevant code that produces the arrows:

    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);
    }

I can animate the rotation of these arrows using a JavaScript timer:

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

That is tied to an event:

    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 );

As you can tell from the code that is commented out, I would like to do the animation with an SVG animationTransform. What I have works just fine, but it would be neat to know how to make animate Transforms work.

I have abstracted away the working code to here:

<!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>

The problem with this use of animateTransform is that the arrow doesn't rotate nicely, the way it does when I simply update the rotate(45) CSS transform in the path.

Another problem I have is that I want to make this happen when the user clicks a button, sort of with this code (which is broken):

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);
}

I've verified that this code adds the animateTransform to the proper location of the DOM. However, it seems that adding the animateTransform to an existing path object doesn't result in the path being animated. I've read the spec, but it's big and I may have missed something. Do I need to delete the old path object and add a new one, rather than just adding the animate transform? Probably. I haven't tried that yet.

So my question:

  1. How can I get the animateTransform to properly rotate my arrow? I've tried fiddling with the values in the from= and the to= and I can never get the arrow rotating in the center.

  2. Is there a way to add the animateTransform to an existing path, or do I need to delete the current path and add a new one?

The full code is here: https://jsfiddle.net/simsong/9udcga6r/1/

解决方案

What sticks out in your code is the lack of a value for begin in your <animateTransform>. begin values are pretty versatile.

  • You can set them to "0s", which is the default, and coincides with the time the document timeline begins (basically the window load event). It is not the time when you later interactively add the tag. At that time, 0s will have passed.

  • You can set them to "indefinite", so the animation does not start. This is usually used in conjunction with a script-based trigger, for example SVGAnimationElement.beginElement().

  • You can set them to an arbitrary event. "myButton.click" will start the animation when the element with id="myButton" receives a click.

The syntax also provides for start time lists and delays. It is well worth studying the syntax in the spec to find out what you can do. (The only part of the time value spec not implemented by browsers is the wallclock() function.)

For the rotation center, you need to look closely at what you transform, and what you rotate. As your code stands, the <animateTransform> replaces the transform attribute on your element in its entirety. What you probably want is a supplemental transformation that is added to the end of the existing transform list. This is steered by the attribute additive="sum" (default is "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>

This will rotate the element around the (0, 0) point in local userspace coordinates, 100 times in a row, from 0 to 180 degrees. Three things to look carefully into:

  1. The center of rotation must be set in local userspace coordinates, after the application of all previous transforms, as it is added to the end of the transform list. (Transformations of coordinate systems, not of objects drawn into them.) Wht this amounts to is: it is the same coordinate system as the one used by the path data. The center of that path is at the coordinate system origin.

  2. The transformation adds to the underlying element transform. This means, it does not replace the initial 45deg rotation, but adds to it. Therefore it is generally a good idea to start additive transforms with an identity transform.

  3. As you defined, the rotation goes from 0 to 180 degrees, and then immediately restarts. This means it will jump back to its initial position without delay. If you want a smooth movement, either rotate from 0 to 360 degrees, or set the attribute accumulate="sum", which will start the next iteration at the end of the previous one.

这篇关于使用SVG animateTransform旋转SVG中已翻译SVG内的路径的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆