使用Raphael JS进行拖放和形状旋转

4
我正在使用RaphaelJS 2.0在一个div中创建几个形状。每个形状都需要能够在div的范围内独立地拖放。双击形状后,该形状需要旋转90度。然后可以再次拖放和旋转。
我已经将一些代码加载到fiddler上:http://jsfiddle.net/QRZMS/。基本上是这样的:
    window.onload = function () {

        var angle = 0;

        var R = Raphael("paper", "100%", "100%"),
            shape1 = R.rect(100, 100, 100, 50).attr({ fill: "red", stroke: "none" }),
            shape2 = R.rect(200, 200, 100, 50).attr({ fill: "green", stroke: "none" }),
            shape3 = R.rect(300, 300, 100, 50).attr({ fill: "blue", stroke: "none" }),
            shape4 = R.rect(400, 400, 100, 50).attr({ fill: "black", stroke: "none" });
        var start = function () {
            this.ox = this.attr("x");
            this.oy = this.attr("y");
        },
        move = function (dx, dy) {
            this.attr({ x: this.ox + dx, y: this.oy + dy });
        },
        up = function () {

        };
        R.set(shape1, shape2, shape3, shape4).drag(move, start, up).dblclick(function(){
            angle -= 90;
            shape1.stop().animate({ transform: "r" + angle }, 1000, "<>");
        });


    }

拖放功能已经可以使用,而且其中一个形状可以在双击时旋转。然而,有两个问题/问题:
  1. 如何将旋转自动附加到每个形状上,而不必将每个项目引用硬编码到旋转方法中?即我只想绘制一次形状,然后自动公开它们的相同行为,这样它们可以分别独立地拖动/放置/旋转,而无需显式地将该行为应用于每个形状。
  2. 形状旋转后,它将无法正确拖动 - 好像拖动鼠标移动与形状的原始方向有关,而不是在旋转形状时更新。如何使其正常工作,以便可以轻松地多次拖动和旋转形状?
非常感谢任何指南!
6个回答

13

我已经尝试多次理解新的变换引擎,但一直没有成功。因此,我回到了最基本的原理。

经过几次尝试,我终于成功地拖放了一个经历过多次变换的对象,在弄清不同变换(t、T、...t、...T、r、R等)的影响后。

所以,这就是解决问题的关键。

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
}

就是这样。非常简单,我相信很多人已经想到了,但也许有人会觉得有用。

以下是一个供您测试的代码片段

还有,这个是对最初问题的可行解决方案。


1
太好了,感谢您的坚持 - 我非常感激。老实说,我的 Raphael 初探并不顺利。最多只有简短的说明文档(例如,尝试找到任何解释如何使用 transform 方法中的矩阵部分的内容),而且网络上的支持非常少,这对像我这样的新手来说非常痛苦。也许只是因为 v2 是如此新和不同,或者可能真的是针对可以解构源代码的严肃 JavaScript 专家,谁知道呢。不过再次感谢,这真的很有帮助,我完全感激! - Dan
2
丹,你必须考虑 Raphael 库试图做什么。它将浏览器的 SVG(和 VML)功能暴露给 JavaScript。因此,它所做的很多工作都是对底层 SVG 元素的直接暴露。这正是转换部分的作用。如果你正在寻找关于它们如何工作的信息,特别是矩阵变换方面,你需要搜索“SVG 矩阵变换”,而不是“Raphael 矩阵变换”。请尝试访问 http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Matrix_Algebra 或 http://www.mecxpert.de/svg/transform.html。 - amadan
Fiddle在FF7中无法工作。第一次拖动后,形状和鼠标的相对位置不同步。 - OrangeDog
OrangeDog,你能详细说明一下吗?我已经在IE8.0.6001、Chrome 15.0.874和Firefox 8.0上进行了测试,看起来一切正常。 - amadan
1
OrangeDog,你是对的,他在fiddle示例中忘记重置拖动起始位置了,只需用以下代码替换拖动开始函数即可使其正常工作:function drag_start(e) { ox = 0; oy = 0; }; - Hogberg
好的,Hogberg。为了辩护,我使用了第二个小提琴,即已经解决的方案进行了测试。在那个方案中,我在mouseup函数中重置了ox和oy。我现在已经更新了示例。所以,万岁 - 我们都是正确的。请接受我的赞扬。 - amadan

4

3
如amadan所建议的那样,当多个事物具有相同的(初始)属性/特性时,创建函数通常是一个好主意。这确实是您第一个问题的答案。至于第二个问题,则有点棘手。
当Rapheal对象旋转时,坐标平面也会旋转。由于某种原因,dmitry和其他一些网络来源似乎都同意这是正确的实现方式。我像你一样不同意。我没有找到一个全面的好解决方案,但我确实设法创建了一个解决方法。我将简要解释一下,然后展示代码。
  • 创建自定义属性以存储旋转的当前状态
  • 根据该属性,决定如何处理移动。
只要您只会将形状旋转90度(如果不是,它就变得更加困难),您就可以确定如何操作坐标。
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) {
}

我很难想出一个简明扼要的方式来解释drag_move如何工作。我认为最好是看看代码,了解它是如何工作的。基本上,你只需要弄清楚x和y变量从这个新的旋转状态下如何处理。如果没有绘制大量图形,我不确定我是否能够清晰地表达。 (我经常需要侧着头来弄清楚它应该做什么)。
但这种方法也有一些缺点:
  • 它只适用于90度的旋转(需要进行更多的计算才能实现45度的旋转,更别说任意角度的旋转了)
  • 在旋转后拖动开始时会有轻微的移动。这是因为拖动采用了旋转后的旧的x和y值。对于这种大小的形状来说,这并不是一个巨大的问题,但对于更大的形状,你将真正开始注意到形状跳跃在画布上。
我假设您使用transform的原因是可以动画旋转。如果这不是必要的,那么你可以使用.rotate()函数,它总是围绕元素的中心旋转,因此可以消除我提到的第二个缺点。
这不是一个完整的解决方案,但它肯定会让你走上正确的道路。我很想看到一个完整的工作版本。
我还在jsfiddle上创建了一个版本,你可以在这里查看: http://jsfiddle.net/QRZMS/3/ 祝你好运。

我基本上已经达到了你上面描述的同样的地步。事实上,你得到的结果和我一样。移动而不旋转是有效的,第一次旋转也是有效的,但是后续的旋转越来越不正确。虽然如此,能得到确认还是很好的。给你点赞。 - amadan
谢谢Adam。我们正在接近目标,但是使用Raphael实现这种功能令人失望的困难。我在想是否选择了正确的工具来完成此任务,因为我需要对物体进行拖放和一些相对复杂的操作。如果要实现这个目标如此困难,那么我认为其他东西也将会非常困难。不过我现在会继续坚持下去,感谢你的帮助! - Dan

1

我通常为我的形状创建一个对象,并将事件处理写入该对象。

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

在上面的代码中,构造函数不仅创建了矩形,还设置了双击事件。
需要注意的一件事是,对象的引用存储在“that”中。这是因为“this”引用取决于作用域。在dblClick函数中,我需要引用对象中的rect和angle值,所以我使用存储的引用that.rect和that.angle 请参见此example(从稍微有点问题的先前实例更新)
可能有更好的方法来满足您的需求,但这应该适合您。
希望它有所帮助,
尼克
补充说明:如果你真的卡在这里了,而且可以不用Raphael2提供的一些功能,我建议回到Raphael 1.5.x。Raphael2刚刚添加了变换功能,旋转/平移/缩放代码在1.5.2中完全不同(也更容易)。
看着我,更新我的帖子,希望得到好评...

我感到很尴尬...我的fiddle中有一个错别字。角度应该是对象的一部分,并且每个实例都应该单独递增。我已经更新了fiddle,现在应该可以正常工作了。这要归咎于同时打开多个浏览器 :( - amadan
Fiddle有点困难:(,请尝试使用这个链接代替http://jsfiddle.net/vPSuR/。 - amadan
哈哈,别担心尼克 - 这是为了尝试! - Dan
这里有一个针对它提出的错误,以及Dmetri的评论。这是正确的行为(这可能是真的,但往往不符合直觉)。在2.0中,拖放和旋转并不兼容 :( https://github.com/DmitryBaranovskiy/raphael/issues/102 - amadan
目前的最后想法...http://jsfiddle.net/vPSuR/。我已经修改了移动函数以根据应用的旋转改变矩形位置的修改,这样效果会更好一些。但是旋转并没有保持在矩形中心,所以这不是一个解决方案。今晚我可能会再次研究这个问题。 - amadan
显示剩余4条评论

1
如果您不想使用ElbertF库,可以将笛卡尔坐标转换为极坐标。
然后您必须添加或删除角度,并再次转换为笛卡尔坐标。
我们可以通过矩形旋转和移动来看这个例子。 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

我的第一个想法是使用getBBox(false)来捕获对象变换后的x、y坐标,然后从画布中删除原始的Raphael对象,再使用getBBox(false)返回的坐标数据重新绘制对象。这是一种hack方法,但我已经让它工作了。

需要注意的一点是:由于getBBox(false)返回的对象是角落坐标(x,y),因此您需要通过以下方式计算重新绘制对象的中心... x = box['x'] + (box['width'] / 2); y = box['y'] + (box['height'] / 2);

其中 box = shapeObj.getBBox(false);

解决同样问题的另一种方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接