Resizing and dragging SVG polygons dynamically

user1159545 picture user1159545 · Jan 20, 2012 · Viewed 7.7k times · Source

I am struggling to find a way for resizing and dragging a svg polygon dynamically with the mouse. Unfortunately jQueryUi does not work for svg elements. I also checked the Raphael library, but cant find any documentation/snippets on how to realize this.

Besides using SVGs, is there any other way to resize and dragg a polygon graphic dynamically?

Answer

Kevin Reid picture Kevin Reid · Jan 21, 2012

You can work with the SVG elements yourself. In particular, you can find the points of the polygon and add HTML-element handles which are made draggable with jQuery. (I'm assuming the problem you're having is that jQuery UI doesn't work with the SVG positioning model.) Here's a complete example I just wrote (tested in Safari 5 and Firefox 9).

(Disclaimer: I am not a regular user of jQuery; this code may be unidiomatic in ways besides not using jQuery for everything.)

<!DOCTYPE html>
<html><head>
  <title>untitled</title>

  <style type="text/css" media="screen">
    .handle {
      position: absolute;
      border: 0.1em solid black;
      width: 1em;
      height: 1em;
    }
  </style>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js"></script>

  <script type="text/javascript">
  function draggablePolygon(polygon) {
    var points = polygon.points;
    var svgRoot = $(polygon).closest("svg");
    
    for (var i = 0; i < points.numberOfItems; i++) {
      (function (i) { // close over variables for drag call back
        var point = points.getItem(i);

        var handle = document.createElement("div");
        handle.className = "handle";
        document.body.appendChild(handle);

        var base = svgRoot.position();
        // center handles over polygon
        var cs = window.getComputedStyle(handle, null);
        base.left -= (parseInt(cs.width) + parseInt(cs.borderLeftWidth) + parseInt(cs.borderRightWidth))/2; 
        base.top -= (parseInt(cs.height) + parseInt(cs.borderTopWidth) + parseInt(cs.borderBottomWidth))/2; 

        handle.style.left = base.left + point.x + "px";
        handle.style.top = base.top + point.y + "px";

        $(handle).draggable({
          drag: function (event) {
            setTimeout(function () { // jQuery apparently calls this *before* setting position, so defer
              point.x = parseInt(handle.style.left) - base.left;
              point.y = parseInt(handle.style.top) - base.top;
            },0);
          }
        });
      }(i));
    }
  }
  </script>
</head><body>
  <p>
    (Offset to test)
    <svg id="theSVG" width="500" height="500" style="border: 2px inset #CCC;">
      <polygon id="x" points="200,200 100,100 100,1" fill="green" />
      <polygon id="y" points="200,200 100,100 1,100" fill="red" />
    </svg>
  </p>
  <script type="text/javascript">
    draggablePolygon(document.getElementById("x"));
    draggablePolygon(document.getElementById("y"));
  </script>

</body></html>

You could also attach an event handler to the SVG polygon and implement dragging (I tested and this could work), but then you would have to click inside the current boundaries of the polygon, which is an atypical UI and possibly tricky, and implement your own hit testing.

For this, you would add an onmousedown handler to the polygon element. Then retrieve its points, find which is in range of the click position, store the initial mouse position, and then have an onmousemove handler modify the x and y of the point as the mouse position changes.