Raphael JS : how to move/animate a path object?

2019-01-17 02:12发布

问题:

Somehow this doesn't work...

var paper = Raphael("test", 500, 500);

var testpath = paper.path('M100 100L190 190');

var a = paper.rect(0,0,10,10);
a.attr('fill', 'silver');

a.mousedown( function() {
  testpath.animate({x: 400}, 1000);
});

I can move rects this way but not paths, why is that, and how do I move a path object then?!

回答1:

With the latest version of Raphael, you can do this:

var _transformedPath = Raphael.transformPath('M100 100L190 190', 'T400,0');
testpath.animate({path: _transformedPath}, 1000);

This saves you from the trouble of having to clone a temp object.



回答2:

It seems a path object doesn't get a x,y value - so your animation probably still runs, but does nothing. Try instead animating the path function:

testpath.animate({path:'M400 100L490 190'},1000);

It makes it a bit trickier to write the animation, but you have the benefit of getting rotation and scaling for free!

BTW: I'm sure this is just an example, but in your above code testpath gets put in the global scope because you don't initialize as var testpath



回答3:

Solved, with thanx to Rudu!

You need to create a new path to animate to. You can do this with clone() and then apply the transformations to that clone. Seems very complex for a simple move like this, but it works...

var paper = Raphael("test", 500, 500);

var testpath = paper.path('M100 100L190 190');

var a = paper.rect(0,0,10,10);
a.attr('fill', 'silver');

a.mousedown( function() {

  var temp = testpath.clone();
  temp.translate(400,0);
  testpath.animate({path: temp.attr('path')}, 1000);
  temp.remove();

});


回答4:

TimDog answer was best solution.

In addition, just remember, transform string in this case means, that it will add 400 points to every path point/line X coordinate, and 0 points to every Y coordinate.

That means, M100 100L190 190 will turn into M500 100L590 190.

So, if you need to move a path element to another position, the difference between current position and new position coordinates should be calculated. You can use first element to do that:

var newCoordinates = [300, 200],
curPos = testpath.path[0],
newPosX = newCoordinates[0] - curPos[1],
newPosY = newCoordinates[1] - curPos[2];

var _transformedPath = Raphael.transformPath(testpath.path, "T"+newPosX+","+newPosY);
testpath.animate({path: _transformedPath});

Hope this will help someone.



回答5:

Here's some code that generalises the best of the above answers and gives Raphael paths a simple .attr({pathXY: [newXPos, newYPos]}) attribute similar to .attr({x: newXPosition}) and .animate({x: newXPosition}) for shapes.

This lets you move your path to a fixed, absolute position or move it by a relative amount in a standard way without hardcoding path strings or custom calculations.


Edit: Code below works in IE7 and IE8. An earlier version of this failed in IE8 / VML mode due to a Raphael bug that returns arrays to .attr('path') in SVG mode but strings to .attr('path') in VML mode.


Code

Add this code (Raphael customAttribute, and helper function) after defining paper, use as below.

paper.customAttributes.pathXY = function( x,y ) {
  // use with .attr({pathXY: [x,y]});
  // call element.pathXY() before animating with .animate({pathXY: [x,y]})
  var pathArray = Raphael.parsePathString(this.attr('path'));
  var transformArray = ['T', x - this.pathXY('x'), y - this.pathXY('y') ];
    return { 
      path: Raphael.transformPath( pathArray, transformArray) 
    };
};
Raphael.st.pathXY = function(xy) { 
   // pass 'x' or 'y' to get average x or y pos of set
   // pass nothing to initiate set for pathXY animation
   // recursive to work for sets, sets of sets, etc
   var sum = 0, counter = 0;
   this.forEach( function( element ){
     var position = ( element.pathXY(xy) );
     if(position){
       sum += parseFloat(position);
       counter++;
     }
   });
   return (sum / counter);
};
Raphael.el.pathXY = function(xy) {
   // pass 'x' or 'y' to get x or y pos of element
   // pass nothing to initiate element for pathXY animation
   // can use in same way for elements and sets alike
   if(xy == 'x' || xy == 'y'){ // to get x or y of path
     xy = (xy == 'x') ? 1 : 2;
     var pathPos = Raphael.parsePathString(this.attr('path'))[0][xy];
     return pathPos;
   } else { // to initialise a path's pathXY, for animation
     this.attr({pathXY: [this.pathXY('x'),this.pathXY('y')]});
   }
};

Usage

For absolute translation (move to fixed X,Y position) - Live JSBIN demo

Works with any path or set of paths including sets of sets (demo). Note that since Raphael sets are arrays not groups, it moves each item in the set to the defined position - not the centre of the set.

// moves to x=200, y=300 regardless of previous transformations
path.attr({pathXY: [200,300]});

// moves x only, keeps current y position
path.attr({pathXY: [200,path.pathXY('y')]});

// moves y only, keeps current x position
path.attr({pathXY: [path.pathXY('x'),300]});

Raphael needs to handle both x and y co-ordinates together in the same customAttribute so they can animate together and so they stay in sync with each other.

For relative translation (move by +/- X,Y) - Live JSBIN demo

// moves down, right by 10
path.attr({pathXY: [ path.pathXY('x')+10, path.pathXY('y')+10 ]},500);

This also works with sets, but again don't forget that Raphael's sets aren't like groups - each object moves to one position relative to the average position of the set, so results may not be what are expected (example demo).


For animation (move a path to relative or absolute positions)

Before animating the first time, you need to set the pathXY values, due to a bug/missing feature up to Raphael 2.1.0 where all customAttributes need to be given a numeric value before they are animated (otherwise, they will turn every number into NaN and do nothing, failing silently with no errors, or not animating and jumping straight to the final position).

Before using .animate({pathXY: [newX,newY]});, run this helper function:

somePath.pathXY();


回答6:

Yet another way is to use "transform" attribute:

testpath.animate({transform: "t400,0"}, 1000);

to move the path to the right by 400px, relative to the original position.

This should work for all shapes, including paths and rectangles.

Note that:

  • "transform" attribute is independent of x, y, cx, cy, etc. So these attributes are not updated by the animation above.
  • The value of "transform" attribute is always based on the original position, not the current position. If you apply the animation below after the animation above, it will move it 800px to the left relatively, instead of moving it back to its original position.

    testpath.animate({transform: "t-400,0"}, 1000);