【问题标题】:Drag, drop and shape rotation with Raphael JS使用 Raphael JS 进行拖放和形状旋转
【发布时间】:2011-12-28 06:42:24
【问题描述】:

我正在使用 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. 如何自动将旋转附加到每个形状上,而无需将每个项目引用硬编码到旋转方法中? IE。我只想绘制一次形状,然后让它们全部自动暴露于相同的行为,因此它们可以独立地拖放/旋转,而无需将该行为显式应用于每个形状。

  2. 旋转形状后,它不再正确拖动 - 好像拖动鼠标移动与形状的原始方向有关,而不是在旋转形状时更新。我怎样才能让它正常工作,以便可以无缝地多次拖动和旋转形状?

非常感谢您的任何指点!

【问题讨论】:

    标签: drag-and-drop raphael rotation


    【解决方案1】:

    我已经尝试过多次尝试新的转换引擎,但都无济于事。所以,我又回到了第一原则。

    在尝试计算出不同转换的影响之后,我终于设法正确拖放了一个经过多次转换的对象 - 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
    }
    

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

    这里有一个fiddle 供你玩。

    ...和this 是最初问题的有效解决方案。

    【讨论】:

    • 太好了,感谢坚持不懈的amadan - 我真的很感激。老实说,我最初对拉斐尔的尝试并不好。该文档充其量是简短的(例如,尝试找到解释如何使用转换方法的矩阵部分的任何内容)并且网络上的支持很少,这只会让像我这样的新手感到痛苦。也许只是 v2 是如此的新和如此不同,或者它真的是针对可以将源代码分开的严肃的 javascript 重量级人物,谁知道呢。再次感谢,这真的很有帮助,我非常感谢!
    • Dan,你必须考虑一下 Raphael 图书馆想要做什么。它将浏览器的 SVG(和 VML)功能暴露给 javascript。因此,它所做的大部分工作都是直接暴露底层 SVG 元素。这正是转换部分。如果您正在寻找有关它们如何工作的信息,尤其是矩阵变换,您需要在谷歌上搜索“SVG 矩阵变换”而不是“Raphael 矩阵变换”。试试commons.oreilly.com/wiki/index.php/SVG_Essentials/…mecxpert.de/svg/transform.html
    • Fiddle 在 FF7 中不起作用。第一次拖动后,形状和鼠标的相对位置不同步。
    • OrangeDog,你能详细说明一下吗?我已经在 IE8.0.6001、chrome 15.0.874 和 Firefox 8.0 上进行了测试,似乎运行良好。
    • OrangeDog,你说得对,他忘记在小提琴示例中重置拖动开始位置,只需将拖动开始替换为这个就可以了:function drag_start(e) { ox = 0; oy = 0; };
    【解决方案2】:

    我通过在值更改时重新应用所有转换解决了拖动/旋转问题。我为它创建了一个插件。

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

    在这里演示:

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

    【讨论】:

      【解决方案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 值,它们已被旋转。对于这种大小的形状来说,这不是什么大问题,但是更大的形状你会真正开始注意到在画布上跳跃的形状。

      我假设您使用变换的原因是您可以为旋转设置动画。如果这不是必需的,那么您可以使用.rotate() 函数,它始终围绕元素的中心旋转,因此可以消除我提到的第二个缺点。

      这不是一个完整的解决方案,但它绝对能让您走上正确的道路。我很想看到一个完整的工作版本。

      我还在 jsfiddle 上创建了一个版本,您可以在此处查看:http://jsfiddle.net/QRZMS/3/

      祝你好运。

      【讨论】:

      • 我和你上面描述的差不多。事实上,你得到的结果和我一样。在没有旋转的情况下移动,第一次旋转有效,但随后的旋转越来越不正确。不过很高兴得到确认。投赞成票。
      • 谢谢亚当。我们已经到了那里,但与拉斐尔一起实现这种事情令人失望地困难。我实际上想知道我是否为这里的工作选择了正确的工具,因为我需要通过拖放对象来做一些相对复杂的事情。如果要做到这一点是如此困难,那么我认为其他的东西会太难了。不过我会坚持现在,谢谢你的努力!
      【解决方案4】:

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

      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 函数中,我需要引用对象中的 rectangle 值,因此我使用存储的引用 that.rect 和 那个角度

      看到这个example(从一个有点狡猾的先前实例更新)

      可能有更好的方法来满足您的需求,但这应该适合您。

      希望对你有帮助,

      尼克


      附录:Dan,如果您真的坚持这一点,并且可以在没有 Raphael2 提供给您的某些东西的情况下生活,我建议您回到 Raphael 1.5.x。变换刚刚添加到 Raphael2,旋转/平移/缩放代码在 1.5.2 中完全不同(并且更容易)。

      看着我,更新我的帖子,希望有因果报应......

      【讨论】:

      • 谢谢尼克。我会看看这个,但在最初检查那个小提琴页面时,这些形状的行为仍然很奇怪。它们似乎以不同的增量旋转,并且拖放无处不在。
      • 我很尴尬.. 我的小提琴有一个错字。角度应该是对象的一部分,并且每个实例都单独增加。我已经更新了小提琴,它现在应该可以正常工作了。怪它打开了多个浏览器:(
      • 小提琴有点难:(试试这个链接jsfiddle.net/vPSuR
      • 实际上,DnD 第一次工作,然后有点发疯。我撤回了我糟糕的解决方案,并为浪费您的时间而道歉。
      • 哈哈,尼克别担心——这是为了尝试!
      【解决方案5】:

      如果您不想使用 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);?
      

      演示
      http://jsfiddle.net/Ef83k/74/

      【讨论】:

        【解决方案6】:

        我的第一个想法是使用 getBBox(false) 捕获变换后对象的 x,y 坐标,然后从画布中 removeChild() 原始 Raphael obj,然后使用 getBBox(false) 中的坐标数据重新绘制对象)。一个黑客,但我有它的工作。

        但请注意:由于 getBBox( false ) 返回的对象是对象的 CORNER 坐标 (x, y),因此您需要通过执行...来计算重新绘制对象的中心 x = 盒子['x'] + ( 盒子['width'] / 2 ); y = box['y'] + (box['height'] / 2 );

        在哪里 盒子 = shapeObj.getBBox( 假 );

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-03-05
          • 1970-01-01
          • 2010-11-23
          • 1970-01-01
          相关资源
          最近更新 更多