Draggable SVG element

A common form of computer interaction is the ability to move an element on the screen by clicking on it and dragging it. This can achieved relatively easily in an SVG as below. I used this techique a lot for the interactive demos in my SVG tutorial.

Set up

First we start with some objects we want to move and give them the class "draggable", for example:

<rect class="draggable"
      x="30" y="30"
      width="80" height="80"
      fill="blue" />
<rect class="draggable"
      x="160" y="50"
      width="50" height="50"
      fill="green" />

This doesn't do very much, but it means we can style the elements with CSS. For example, it's quite important to let the user know that an object can be dragged, and the most common way to indicate that is to change the cursor:

<style>
    .draggable {
        cursor: move;
    }
</style>

When the mouse is moved over any element with the class 'draggable', the cursor will change to a 'move' cursor (normally a hand or crossed arrows).

Selecting an element

The click-and-drag functionality is split into two obvious events. The first is the click, which is triggered when the left mouse button is pressed down while the cursor is over an object. When this happens, we want to select that object by assigning it to a variable. Perhaps less obviously, we also want to record the position of the mouse so that when it moves, we know by how much it has moved.

To the elements you want to be able to move, add an onmousedown trigger that calls a function selectElement. It should pass the event, evt, so we know which element triggered it and where the mouse was at the time. For example:

<rect class="draggable"
      x="30" y="30"
      width="80" height="80"
      fill="blue"
      transform="matrix(1 0 0 1 0 0)"
      onmousedown="selectElement(evt)"/>

In order to move the element we also need to add a transform matrix. We could just use transform="translate(0 0)", but using a matrix is more versatile. The matrix is currently set at (1 0 0 1 0 0), which is the equivalent of the identity matrix (so has no effect).

Now we add the code to deal with selecting the element.

<script type="text/ecmascript"> <![CDATA[
  var selectedElement = 0;
  var currentX = 0;
  var currentY = 0;
  var currentMatrix = 0;

  function selectElement(evt) {
    selectedElement = evt.target;
    currentX = evt.clientX;
    currentY = evt.clientY;
    currentMatrix = selectedElement.getAttributeNS(null, "transform").slice(7,-1).split(' ');
    
      for(var i=0; i<currentMatrix.length; i++) {
        currentMatrix[i] = parseFloat(currentMatrix[i]);
      }

    selectedElement.setAttributeNS(null, "onmousemove", "moveElement(evt)");
  }
]]> </script>

When we mousedown on the element, we record what element it is and what it's current transformation is (which we save as an array of floats for easy manipulation). We also record the x and y coordinates of the mousedown event because we need to be able to calculate relative mouse movements. Finally, we add an new event trigger for onmousemove, which calls a function, moveElement().

Dragging an element

The function that deals with moving the element looks like this:

function moveElement(evt){
  dx = evt.clientX - currentX;
  dy = evt.clientY - currentY;
  currentMatrix[4] += dx;
  currentMatrix[5] += dy;
  newMatrix = "matrix(" + currentMatrix.join(' ') + ")";
            
  selectedElement.setAttributeNS(null, "transform", newMatrix);
  currentX = evt.clientX;
  currentY = evt.clientY;
}

This function finds the new coordinates of the mouse and how this position differs from the previously recorded position. It then updates the 4th and 5th (counting from 0) numbers in array representing the selected element's transformation, which corresponds to a translation dx units left and dy units down. The array is used to generate a new matrix which is added to the selected element. Finally, the new position of the mouse is recorded.

Deselecting an element

If you view the SVG now, you'll be able to select and element and move it, but you won't be able to 'let go' and select a new one. To deselect we add a new function:

function deselectElement(evt){
  if(selectedElement != 0){
    selectedElement.removeAttributeNS(null, "onmousemove");
    selectedElement.removeAttributeNS(null, "onmouseout");
    selectedElement.removeAttributeNS(null, "onmouseup");
    selectedElement = 0;
  }
}

The function should be fairly self-explanatory, we just remove the onmousemove trigger and set the selectElement variable to 0. It also removes onmouseout and onmouseup triggers, which we haven't yet added. They can be added when the element is selected along with the onmousemove trigger and should call the deselect function, e.g.:

selectedElement.setAttributeNS(null, "onmouseout", "deselectElement(evt)");
selectedElement.setAttributeNS(null, "onmouseup", "deselectElement(evt)");

You can now drag the elements. At the moment if you drag an element to one that is 'above' it in z, then dragging will stop. At a later date I might add a function to move a selected element to the top (i.e. so it is drawn last). To see how this might be done take a look at Moving SVG elements in Z.

 

AttachmentSize
drag_cursor.svg413 bytes
drag_elements.svg2.19 KB

Comments

It must be challenging to step back and write a description for beginners.  

Your write-up for dragging svg shapes is just about perfect for me.

The step-by step breakdown makes the parts digestible, the attention to formatting and naming contributes a lot to understandability, the isolated example given at the end helps to firm up a holistic view, and the way you touch on topics that could be hugely distracting, such as the transformation matrix usage, with just enough information to lead to success, is really admirable.

Thank you for a great description. 

Thanks for your kind words Michael, I appreciate you taking the time to write. It's very encouraging to hear that this has been of use and makes me want get on with writing a full SVG tutorial. I'm not that far beyond a beginner, so it's not much of a step to write something I think I would have found useful.

All the best, Peter

Thanks for this great post! Any way to get this to work with a group <g> of elements? I have more complex shapes made through <g> that I want to move together as a unit.

Thanks. I've actually answered a similar question on StackOverflow (http://stackoverflow.com/questions/7777010/svg-dragging-for-group/7778990#7778990). If you look at the code for the example you should be able to work it out. The only real difference is to select the parent element when you click on a shape. One day I'll get around to writing an proper tutorial here.

I'd like to second Michael Everitt's sentiments precisely.  Thanks-a-bunch for this well delivered info.

One question .. what would I need to tweak to allow the selected shape to only be able to move along the x OR y axis.

Well ... I'm really most interested in the X axis ... I include the Y axis out of sheer curiosity :)

Nevermind about X or Y axis only movement. I found a simple solution.

Changed "currentMatrix[5] += dy"  to  "currentMatrix[5] = dy" (+= to =). Seems to work nicely ...  let me know if there are reasons to do it differently :)

Next question though, is about dragging <def>'d shapes.  It seems to only work if I explicitly call out a <rect> ...  the dragging doesn't seem to work on shapes drawn via <use> ...  thoughts?

Your attachement drag_elements.svg have a small bug.

function deselectElement(evt) {
if(selectedElement != 0){
selectedElement = 0;
selectedElement.removeAttributeNS(null, "onmousemove");
selectedElement.removeAttributeNS(null, "onmouseout");
selectedElement.removeAttributeNS(null, "onmouseup");
}
}

Note that you are clearing selectedElement before using it to remove the handlers.

Thanks for pointing out the mistake Nuno. I must have forgotten to update the code. I've updated it now and it should work properly.

Very very thanks to you.. I dont know how to say thanks to you.. Because I was looking for svg code with moving elements. I saw many site with moving elements in svg. But those were working with me. But this code is working well with me.. 

But if we drag fastly,, it causes mose control ... why..  

by Aslam.Pk

Thanks for the excellent introduction!  I had one issue: the script stops responding if you move the mouse fast enough to get the cursor outside the bounds of the element before the code in moveElement finishes executing.  

I was able to resolve this problem by simply replacing the line:

 selectedElement.setAttributeNS(null, "onmousemove", "moveElement(evt)");

with:

document.onmousemove = moveElement;

and replacing the line:

selectedElement.removeAttributeNS(null, "onmousemove");

with:

document.onmousemove = null;

This way the script pays attention to all mouse move events in the document, even if the mouse isn't over the element for brief periods as things are moving around.

I found this solution here.

Thank you very much for producing such an extremely useful document!There are two small issues which I wonder if you've thought about. 1) If, while dragging and continuing to hold down the button on the mouse, the cursor leaves the window even briefly and then re-enters, the object is no longer being dragged. I used to do a little programming under the X-Windows system, and it had two features (i) while the mouse button was held down, events were delivered to the original window and (ii) one could determine the state of the mouse buttons in the context of a mouse movement event.  It seems HTML5/javascript/SVG doesn't work that way.2) In Firefox 25.0.1, about a quarter of the time, instead of dragging the object as expected, I find myself dragging a translucent copy of one or both squares.  It is as though some other "drag-and-drop" mechnism is competing with the one you have implemented.   Have you noticed this? Again, thank you and best wishes, Mike Albert  

I noticed the same thing as Michael Albert.  Working on trial and error to attack.  I'm going to look into what Phil said too...sounds possibly germane.  Else, what a great doc, it wraps up a number of points in a  succinct useful form.  Like the writer above mentions, I am trying to ignore Y, and also to limit the range of dragging.

Unfortunately there is a glaring flaw in this. this works because you have created an SVG 400x200 with a viewbox also 400x200,  a pixel as returned by clientX etc is the same size as a pixel in the transformation matrix. However if you took advantage of the S in SVG and had a view box of different dimensions e.g. 4000x2000 or 40x20 (or worse still a different aspect ratio) the shape would not move with the mouse.

See http://www.codedread.com/blog/archives/2005/12/21/how-to-enable-dragging... for a way of overcoming this.

Thanks for your tutorial but it doesn't work on IOS.

Is it possible to do the same thing with a finger and not a mouse ?

...sorry for my english

Post new comment

The content of this field is kept private and will not be shown publicly.