Drag, drop and shape rotation with Raphael JS

前端 未结 6 733
星月不相逢
星月不相逢 2020-12-08 08:45

I\'m using RaphaelJS 2.0 to create several shapes in a div. Each shape needs to be able to be dragged and dropped within the bounds of the div, independently. Upon double

相关标签:
6条回答
  • 2020-12-08 09:15

    As amadan suggests, it's usually a good idea to create functions when multiple things have the same (initial) attributes/properties. That is indeed the answer to your first question. As for the second question, that is a little more tricky.

    When a Rapheal object is rotated, so is the coordinate plane. For some reason, dmitry and a few other sources on the web seem to agree that it's the correct way to implement it. I, like you, disagree. I've not managed to find an all round good solution but I did mange to create a work around. I'll briefly explain and then show the code.

    • Create a custom attribute to store the current state of rotation
    • Depending on that attribute you decide how to handle the move.

    Providing that you are only going to be rotating shapes by 90 degrees (if not it becomes a lot more difficult) you can determine how the coordinates should be manipulated.

    var R = Raphael("paper", "100%", "100%");
    
    //create the custom attribute which will hold the current rotation of the object {0,1,2,3}
    R.customAttributes.rotPos = function (num) {
        this.node.rotPos = num;
    };
    
    var shape1 = insert_rect(R, 100, 100, 100, 50, { fill: "red", stroke: "none" });
    var shape2 = insert_rect(R, 200, 200, 100, 50, { fill: "green", stroke: "none" });
    var shape3 = insert_rect(R, 300, 300, 100, 50, { fill: "blue", stroke: "none" });
    var shape4 = insert_rect(R, 400, 400, 100, 50, { fill: "black", stroke: "none" });
    
    //Generic insert rectangle function
    function insert_rect(paper,x,y, w, h, attr) {
        var angle = 0;
        var rect = paper.rect(x, y, w, h);  
        rect.attr(attr);
    
        //on createion of the object set the rotation position to be 0
        rect.attr({rotPos: 0});
    
        rect.drag(drag_move(), drag_start, drag_up);
    
        //Each time you dbl click the shape, it gets rotated. So increment its rotated state (looping round 4)
        rect.dblclick(function(){
            var pos = this.attr("rotPos");
            (pos++)%4;
            this.attr({rotPos: pos});
            angle -= 90;
            rect.stop().animate({transform: "r" + angle}, 1000, "<>");
        });
    
        return rect;  
    }
    
    //ELEMENT/SET Dragger functions.
    function drag_start(e) {
        this.ox = this.attr("x");
        this.oy = this.attr("y");
    };
    
    
    //Now here is the complicated bit
    function drag_move() {
        return function(dx, dy) {
    
            //default position, treat drag and drop as normal
            if (this.attr("rotPos") == 0) {
              this.attr({x: this.ox + dx, y: this.oy + dy});
            }
            //The shape has now been rotated -90
            else if (this.attr("rotPos") == 1) {
    
                this.attr({x:this.ox-dy, y:this.oy + dx});
            }           
            else if (this.attr("rotPos") == 2) {
                this.attr({x: this.ox - dx, y: this.oy - dy});
            }
            else if (this.attr("rotPos") == 3) {
                 this.attr({x:this.ox+dy, y:this.oy - dx});
    
            }      
        }
    };
    
    function drag_up(e) {
    }
    

    I can't really think of clear concise way to explain how the drag_move works. I think it's probably best that you look at the code and see how it works. Basically, you just need to work out how the x and y variables are now treated from this new rotated state. Without me drawing lots of graphics I'm not sure I could be clear enough. (I did a lot of turning my head sideways to work out what it should be doing).

    There are a few drawbacks to this method though:

    • It only works for 90degree rotations (a huge amount more calculations would be needed to do 45degrees, nevermind any given degree)
    • There is a slight movement upon drag start after a rotation. This is because the drag takes the old x and y values, which have been rotated. This isn't a massive problem for this size of shape, but bigger shapes you will really start to notice shapes jumping across the canvas.

    I'm assuming the reason that you are using transform is that you can animate the rotation. If this isn't necessary then you could use the .rotate() function which always rotates around the center of the element and so would eliminate the 2nd drawback I mentioned.

    This isn't a complete solution, but it should definitely get you going along the correct path. I would be interested to see a full working version.

    I've also created a version of this on jsfiddle which you can view here: http://jsfiddle.net/QRZMS/3/

    Good luck.

    0 讨论(0)
  • 2020-12-08 09:23

    my first thought was to use getBBox(false) to capture the x,y coordinates of the object after transform, then removeChild() the original Raphael obj from the canvas, then redraw the object using the coordinate data from getBBox( false ). a hack but i have it working.

    one note though: since the object the getBBox( false ) returns is the CORNER coordinates ( x, y) of the object you need to calculate the center of the re-drawn object by doing ... x = box['x'] + ( box['width'] / 2 ); y = box['y'] + ( box['height'] / 2 );

    where box = shapeObj.getBBox( false );

    another way to solve the same problem

    0 讨论(0)
  • 2020-12-08 09:28

    I solved the drag/rotate issue by re-applying all transformations when a value changes. I created a plugin for it.

    https://github.com/ElbertF/Raphael.FreeTransform

    Demo here:

    http://alias.io/raphael/free_transform/

    0 讨论(0)
  • 2020-12-08 09:31

    I usually create an object for my shape and write the event handling into the object.

    function shape(x, y, width, height, a)
    {
      var that = this;
      that.angle = 0;
      that.rect = R.rect(x, y, width, height).attr(a);
    
      that.rect.dblclick(function() {
          that.angle -= 90;
          that.rect.stop().animate({
              transform: "r" + that.angle }, 1000, "<>");
      });
      return that;
    }
    

    In the above, the constructor not only creates the rectangle, but sets up the double click event.

    One thing to note is that a reference to the object is stored in "that". This is because the "this" reference changes depending on the scope. In the dblClick function I need to refer to the rect and angle values from my object, so I use the stored reference that.rect and that.angle

    See this example (updated from a slightly dodgy previous instance)

    There may be better ways of doing what you need, but this should work for you.

    Hope it help,

    Nick


    Addendum: Dan, if you're really stuck on this, and can live without some of the things that Raphael2 gives you, I'd recommend moving back to Raphael 1.5.x. Transforms were just added to Raphael2, the rotation/translation/scale code is entirely different (and easier) in 1.5.2.

    Look at me, updating my post, hoping for karma...

    0 讨论(0)
  • 2020-12-08 09:36

    If you don't want to use a ElbertF library, you can transform Cartesian Coordinates in Polar Coordinates.

    After you must add or remove the angle and transform again in Cartesian Coordinate.

    We can see this example with a rect rotate in rumble and moved.

    HTML

    <div id="foo">
    
    </div>
    

    JAVASCRIPT

    var paper = Raphael(40, 40, 400, 400); 
    var c = paper.rect(40, 40, 40, 40).attr({ 
        fill: "#CC9910", 
        stroke: "none", 
        cursor: "move" 
    }); 
    c.transform("t0,0r45t0,0");
    
    var start = function () { 
        this.ox = this.type == "rect" ? this.attr("x") : this.attr("cx");
        this.oy = this.type == "rect" ? this.attr("y") : this.attr("cy");
    }, 
    
    move = function (dx, dy) { 
        var r = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
        var ang = Math.atan2(dy,dx);
        ang = ang - Math.PI/4;
        dx = r * Math.cos(ang);
        dy = r * Math.sin(ang);
    
        var att = this.type == "rect" ? { x: this.ox + dx, y: this.oy + dy} : { cx: this.ox + dx, cy: this.oy + dy };
        this.attr(att);
    }, 
    
    up = function () { 
    }; 
    
    c.drag(move, start, up);?
    

    DEMO
    http://jsfiddle.net/Ef83k/74/

    0 讨论(0)
  • 2020-12-08 09:38

    I've tried several times to wrap my head around the new transform engine, to no avail. So, I've gone back to first principles.

    I've finally managed to correctly drag and drop an object thats undergone several transformations, after trying to work out the impact of the different transformations - t,T,...t,...T,r,R etc...

    So, here's the crux of the solution

    var ox = 0;
    var oy = 0;
    
    function drag_start(e) 
    {
    };
    
    
    function drag_move(dx, dy, posx, posy) 
    {
    
    
       r1.attr({fill: "#fa0"});
    
       //
       // Here's the interesting part, apply an absolute transform 
       // with the dx,dy coordinates minus the previous value for dx and dy
       //
       r1.attr({
        transform: "...T" + (dx - ox) + "," + (dy - oy)
       });
    
       //
       // store the previous versions of dx,dy for use in the next move call.
       //
       ox = dx;
       oy = dy;
    }
    
    
    function drag_up(e) 
    {
       // nothing here
    }
    

    That's it. Stupidly simple, and I'm sure it's occurred to loads of people already, but maybe someone might find it useful.

    Here's a fiddle for you to play around with.

    ... and this is a working solution for the initial question.

    0 讨论(0)
提交回复
热议问题