Draggable SVG elements


3 Apr 2018 Code on Github

Introduction

[This is a completely rewritten version of a post from 1st September 2011]

One of the most common forms of interaction on a computer is clicking and dragging. I use it a lot for interactive demos, such as those in my SVG tutorial, and have a built a library for making simple draggable SVG diagrams.

You can find the code for all the examples on this page here. This article builds up the code required step-by-step, explaining why each element is needed. If you only care about finished code, you can find it here.

This is the SVG I'm going to build on this page. It's relatively simple, but it demonstrates the code works with different element types. Some of the elements have transformations applied to them. For example, the star is rotated. There is also a static rect element which can't be dragged.

Drag

SVG setup

Let's start with a simple SVG with two rect element, one we want to be draggable and another we don't.

<svg xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 30 20">
  <rect x="0" y="0" width="30" height="20" fill="#fafafa"/>
  <rect x="4" y="5" width="8" height="10" fill="#007bff"/>
  <rect x="18" y="5" width="8" height="10"   fill="#888"/>
</svg>

CSS

First give the rect we want to make draggable the class "draggable". We can give the other rect the class static. We can then give the user an idea about what they can interactive with by changing the cursor when they mouseover each element

.static {
  cursor: not-allowed;
}
.draggable {
  cursor: move;
}

Javascript outline

The click-and-drag interaction has two obvious parts: click and drag, but really there are three:

  • Pressing the mouse, when we need to find what element, if any, we have pressed on.
  • Dragging the mouse, when we need to move the element.
  • Releasing the mouse, when we need to release the element so it doesn't continue to move when we move the mouse.

This is fairly straightforward but there a bit of subtlety required to get it right.

Let's start by making a makeDraggable function. This can be in a separate Javascript file or in a script element inside the SVG itself. We call this function when the to the SVG element loads, passing in the load event:

<svg xmlns="http://www.w3.org/2000/svg"
     viewBox="0 0 30 20"
     onload="makeDraggable(evt)">

This function itself gets the SVG element as the event target and binds event listeners to mousedown, mousemove, mouseup, and mouseleave events on the SVG element.

function makeDraggable(evt) {
  var svg = evt.target;
  svg.addEventListener('mousedown', startDrag);
  svg.addEventListener('mousemove', drag);
  svg.addEventListener('mouseup', endDrag);
  svg.addEventListener('mouseleave', endDrag);

  function startDrag(evt) {
  }

  function drag(evt) {
  }

  function endDrag(evt) {
  }
}

Selecting an element

Let's test that we can select and deselect an element. First create a variable selectedElement and set it to null (or something falsy). We add it outside of the dragging function so they can all refer to it, but inside makeDraggable so it's not global.

var selectedElement = false;

Our startDrag function should test whether the target of the mousedown event it's passed has the class draggable, and if so set selectedElement to that element.

function startDrag(evt) {
  if (evt.target.classList.contains('draggable')) {
    selectedElement = evt.target;
  }
}

For now, we'll just make the drag function increment the x attribute of the selected element. Note that we use getAttributeNS and setAttributeNS when use SVG elements. We also have to make sure we convert the attribute into a float before we add 0.1 to it.

I've also add evt.preventDefault(); which blocks any other dragging behaviour. For example, in the SVG at the top of the page, if you drag an element over the text element, it won't highlight the text.

function drag(evt) {
  if (selectedElement) {
    evt.preventDefault();
    var x = parseFloat(selectedElement.getAttributeNS(null, "x"));
    selectedElement.setAttributeNS(null, "x", x + 0.1);
  }
}

The endDrag function just needs to set selectedElement back to null, so we don't keep moving it once the mouse has been released.

function endDrag(evt) {
  selectedElement = null;
}

Now we can click on the draggable (blue) rect, and when we move the mouse, it will slide right. Clicking the static rect and dragging the mouse has no effect.

Dragging an element

Now let's try to actually move the rect with the mouse. We can get the position of the mouse using evt.clientX and evt.clientY, and use these to update the coordinates of the element.

function drag(evt) {
  if (selectedElement) {
    evt.preventDefault();
    var dragX = evt.clientX;
    var dragY = evt.clientY;
    selectedElement.setAttributeNS(null, "x", dragX);
    selectedElement.setAttributeNS(null, "y", dragY);
  }
}

Now if you click and drag the rect, you'll find that it... disappears, or goes somewhere unexpected.

Fixing the coordinates

The problem is that clientX and clientY give the mouse coordinates using the screen coordinate system (which will be something like 300 x 200 pixels, though it depends on how you're viewing the page, since the images are responsive). We need to know the coordinates in SVG space, which is defined by the viewBox attribute, in this case 30 x 20.

To find out how to convert from the screen coordinate system to the SVG coordinate system, we can use the getScreenCTM method of the svg element. This returns the Current Transformation Matrix for the screen, which is an object with six keys, a, b, c, d, e, f.

It's not too important what these values represent; in most cases all we need to know is that if an element has attributes of $(x, y)$, then it will have coordinates on screen of $(ax + e, dy + f)$. So to find the position of the mouse in SVG space, we just calculate the inverse. So let's add a function to do that:

function getMousePosition(evt) {
  var CTM = svg.getScreenCTM();
  return {
    x: (evt.clientX - CTM.e) / CTM.a,
    y: (evt.clientY - CTM.f) / CTM.d
  };
}

Then the drag function becomes:

function drag(evt) {
  if (selectedElement) {
    evt.preventDefault();
    var coord = getMousePosition(evt);
    selectedElement.setAttributeNS(null, "x", coord.x);
    selectedElement.setAttributeNS(null, "y", coord.y);
  }
}

Now when you drag, the rect element moves with your mouse.

Unfortunately, it positions the corner of the rect where your mouse is, even if you select the center of the rect.

Fixing dragging

There are a couple of ways we could fix the dragging issue. I think the easiest is to calculate the offset from where the mouse is first clicked at the top left of the rect. Then, when we set the coordinates of the rect, we can subtract that value.

So calculate the offset in the startDrag function:

var selectedElement, offset;

function startDrag(evt) {
  if (evt.target.classList.contains('draggable')) {
    selectedElement = evt.target;
    offset = getMousePosition(evt);
    offset.x -= parseFloat(selectedElement.getAttributeNS(null, "x"));
    offset.y -= parseFloat(selectedElement.getAttributeNS(null, "y"));
  }
}

And then remove the offset in the drag function.

function drag(evt) {
  if (selectedElement) {
    evt.preventDefault();
    var coord = getMousePosition(evt);
    selectedElement.setAttributeNS(null, "x", coord.x - offset.x);
    selectedElement.setAttributeNS(null, "y", coord.y - offset.y);
  }
}

Now everything should work perfectly.

Until...

Dragging other elements

As we've written it, the drag function works by updating the x and y attributes of the selected element. Unfortunately, most elements don't have x and y attributes. In order to make the function universal, we need to use transforms.

Working with transforms is a little tricky. I did write a version that uses regex to parse the transform attribute for elements, which is a bit hacky, but might be easier to understand. It also might not work if the target element has some convoluted set of transformations already applied.

The version here is better, but might not work on all browsers. The idea is to calculate the offset from the first transform on the element, which should be a translate transform. If the first transform is not a translation, or the element doesn't have a transform, then we add one.

var selectedElement, offset, transform;

function startDrag(evt) {
  if (evt.target.classList.contains('draggable')) {
    selectedElement = evt.target;
    offset = getMousePosition(evt);

    // Get all the transforms currently on this element
    var transforms = selectedElement.transform.baseVal;

    // Ensure the first transform is a translate transform
    if (transforms.length === 0 ||
        transforms.getItem(0).type !== SVGTransform.SVG_TRANSFORM_TRANSLATE) {
      // Create an transform that translates by (0, 0)
      var translate = svg.createSVGTransform();
      translate.setTranslate(0, 0);

      // Add the translation to the front of the transforms list
      selectedElement.transform.baseVal.insertItemBefore(translate, 0);
    }

    // Get initial translation amount
    transform = transforms.getItem(0);
    offset.x -= transform.matrix.e;
    offset.y -= transform.matrix.f;
  }
}

The drag function then updates the translation transform to the mouse position minus the offset:

function drag(evt) {
  if (selectedElement) {
    evt.preventDefault();
    var coord = getMousePosition(evt);
    transform.setTranslate(coord.x - offset.x, coord.y - offset.y);
  }
}

Now we have a makeDraggable function that should work on any element regardless of its type and the transformations applied to it.

The order of the elements stays the same, so the ellipse will always be in front of the rect elements, but behind the others. I'll write a separate tutorial on how to move elements up and down in the z-axis.

Drag

Working on mobile

On final thing: getting mouse events to work on mobile and touch devices. The first thing we need to do is to add handlers for the touch events:

svg.addEventListener('touchstart', startDrag);
svg.addEventListener('touchmove', drag);
svg.addEventListener('touchend', endDrag);
svg.addEventListener('touchleave', endDrag);
svg.addEventListener('touchcancel', endDrag);

Then, because there can be multiple touches, we need to use only the position of the first one. So we have to update the getMousePosition function:

function getMousePosition(evt) {
  var CTM = svg.getScreenCTM();
  if (evt.touches) { evt = evt.touches[0]; }
  return {
    x: (evt.clientX - CTM.e) / CTM.a,
    y: (evt.clientY - CTM.f) / CTM.d
  };
}

Working with groups

There are still a few elements this code won't work with: groups and foreignObjects. The reason is that these elements wrap child elements.

In the case of foreignObjects you can add a style to prevent the child element from responding to mouse events. Otherwise the code will attempt to add transform attributes to them, which won't work.

foreignObject * {
  pointer-events: none;
}

With groups, the groups themselves to do not capture mouse events, so we need to get the group element from the child element.

selectedElement = evt.target.parentNode;

You can find the full code here, which allows for dragging both individual elements and groups (e.g. the star and ellipse).

Drag

Constraining movement

Several people have asked how to restrict the movement of items when dragging. At the very least it would make sense to prevent items from being dragged off screen.

The first thing we need to do is to define the boundary we're going to restrict items' movement to. Here I'm using four variables to define a rectangular boundary. If you want to use a more complex shape then you'll have to look into more complex collision detection.

var boundaryX1 = 10.5;
var boundaryX2 = 30;
var boundaryY1 = 2.2;
var boundaryY2 = 19.2;

In the startDrag function, we test whether the selected element has the class "confine". This is only required if you want some items to be unconstrained.

Then we use the built-in getBBox method to find the bounding box of the element. This returns an object with attributes, x, y, width and height for a box that surrounds the item. We can use this to find the bounding coordinates for the element relative to the boundary.

confined = selectedElement.classList.contains('confine');
if (confined) {
  bbox = selectedElement.getBBox();
  minX = boundaryX1 - bbox.x;
  maxX = boundaryX2 - bbox.x - bbox.width;
  minY = boundaryY1 - bbox.y;
  maxY = boundaryY2 - bbox.y - bbox.height;
}

Note that getBBox doesn't take into account transformations on the element, so it's best to not have elements with existing transformations. You can either apply the transformations to the element directly or wrap everything in a group.

Then, in the drag method, we calculate the position the element is dragged to, and ensure it doesn't exceed the boundary before applying it

var dx = coord.x - offset.x;
var dy = coord.y - offset.y;

if (confined) {
  if (dx < minX) { dx = minX; }
  else if (dx > maxX) { dx = maxX; }
  if (dy < minY) { dy = minY; }
  else if (dy > maxY) { dy = maxY; }
}

transform.setTranslate(dx, dy);

Here's an example of the code in action. The dark grey box represents the restricted area. The ellipse star and word all have the "confine" class name so they cannot leave the box. The rectangle and squiggle don't have the class name, so can move freely.

The complete code can be found here.

Drag

Comments (66)

Michael Everitt on 23 Feb 2012, 5:22 p.m.

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.

Peter on 23 Feb 2012, 11:42 p.m.

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

zass on 11 Apr 2012, 9:44 p.m.

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.

Peter on 11 Apr 2012, 10:05 p.m.

Thanks. I've 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.

mO on 20 Jun 2012, 4:05 p.m.

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 :)

mO on 20 Jun 2012, 4:37 p.m.

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?

Nuno Goncalves on 28 Mar 2013, 2:02 p.m.

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.

Peter on 15 Apr 2013, 12:16 a.m.

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.

Aslam on 4 Jul 2013, 9:49 p.m.

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

Phil on 22 Oct 2013, 8:22 p.m.

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.

Michael Albert on 15 Dec 2013, 10:41 p.m.

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

Dave on 16 Feb 2014, 6:52 a.m.

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.

Anonymous on 18 Feb 2014, 12:29 p.m.

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.

Remi on 22 Nov 2014, 8:39 p.m.

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

Hans Erik Hazelhorst on 10 Jul 2015, 5:33 p.m.

Thank you for this extremely useful introduction. I have an additional question: how can I pass the result of a drag operation to a URL?

My SVG objects (and all Javascript ) are rendered by a database program, and I need to pass information back to the program using a URL with some parameters. How do I get the resulting coordinates back into parameters in the URL?

Hans Erik Hazelhorst on 10 Jul 2015, 8:52 p.m.

Thank you for the excellent introduction on making interactive SVGs. Perhaps you tell me how I can retrieve the position of the svg object after the dragging operation, and return it as a part of a URL?

Karl_Heg on 21 Feb 2016, 10:43 p.m.

The draggable solution in svg or html is a good solution to make
classifications per position X/Y
example:
Horizontal: Fruits/left:600px; Vegetables/800px; Cereales/1000px

Vertical: Poatatoes;Rice;Banana;Mais; Apple;Toatoes;Chicoree

all vertical values: left:400px

set the vertical objects to the right horizontal object/group etc

Is it a possibility to save the changed values/result-document?

Best Regards

Karl_Heg

Alex on 17 Mar 2016, 8:52 p.m.

Thanks a lot. I needed to animate movement of an svg element and had trouble using jquery or css. Your solution using matrix worked for me.

Rustem Zakiev on 11 Oct 2016, 5:22 p.m.

Very helpful! Thank you!

Anonymous on 11 Jan 2017, 7:28 a.m.

Thanks Heaps Peter, great work.

I'm utilising the drag drop and tooltip.

Given some slightly obscure requirements, I've found your work very helpfull in piecing together what I need for my scenario.

Your a champ, thanks again.

Jonathan on 15 Jun 2017, 2:43 a.m.

Well let me say that even after all these years, you just helped a student for his work of end of studies. :)
So thank you very much

Shashank on 15 Jun 2017, 7 a.m.

Hi peter,

Can you please also post a tutorial on "How to do a free transfrom of objects/images in SVG similar to Photoshop Free Transform feature"? I could not find any relevent info on this.

Thanks in advance

Shashank

Jaroslav on 2 Dec 2017, 1:03 a.m.

Hi!

I tried your code with rect elements and with image element. See: http://vysoky.pythonanywhere.com/files/brython/dragndrop-svg-for-peter.html

In google chrome it works fine but in firefox image element's behavior is different. There is some strange firefox self dragging. Sometimes a rectangle starts to behave in the same way. I must select the second rectangle to stop the behavior. I tried it with FF v. 57.0.1 in Linux Mint.

Do you have any idea for correct behavior in firefox too?

Thanks!

Jaroslav

Percival on 3 Dec 2017, 7:35 p.m.

So ... if I have this set up, so I can build a map, from elements, how do I share that built map with someone else? In other words, how do I save all the transforms that have taken place and then hand that map to someone else, who might want to move things around further, and then hand something back to me?

Thank you.

Henrik on 13 Jun 2018, 11:02 a.m.

Thanks Peter, this was just what I needed!
Starting from a basic example and building upon that until you got a working solution, and explaining each step it the way to go.

Abdul Mateen on 20 Jun 2018, 4:13 p.m.

Hi,

First of all, thank you for making this tutorial. I've followed the same steps as you've mentioned but I'm facing an issue.

Reproducing steps:
--------------------------
First time drag the rectangle box somewhere, then try to drag it again or just click it, see it starts from x=0 y=0 coordinates because of the 'offset' value and the 'coord' values are same so, coord.x - offset.x results in 0

Could you please help me fix this issue you can find the fiddle here http://jsfiddle.net/spamrvt1/

Thanks in advance

Peter on 20 Jun 2018, 10:24 p.m.

Hi Abdul,

Thanks for giving such a well documented bug report. It seems I forgot to show the updated drag function. It should be:

function drag(evt) {
if (selectedElement) {
var coord = getMousePosition(evt);
transform.setTranslate(coord.x - offset.x, coord.y - offset.y);
}
}

The full code can be found here:
https://raw.githubusercontent.com/petercollingridge/code-for-blog/master/svg-interaction/draggable/draggable_final.svg

Abdul Mateen on 21 Jun 2018, 7:05 a.m.

Hi Peter,

Thanks for such a quick response, it works now :)

Henrik on 22 Jun 2018, 11:13 a.m.

Hmm, the full code example https://raw.githubusercontent.com/petercollingridge/code-for-blog/master/svg-interaction/draggable/draggable_final.svg, does not contains the multiple touchsupport you included last in your article.
if (evt.touches) {
evt = evt.touches[0];
}

There is also an extra quote-character last on this line:
<rect x="4" y="5" width="8" height="10" fill="#007bff""/>
Under "SVG Setup"

Peter on 22 Jun 2018, 10:21 p.m.

The code including touch support is at:
https://raw.githubusercontent.com/petercollingridge/code-for-blog/master/svg-interaction/draggable/draggable_mobile.svg
Calling the other file draggable_final.svg is a bit misleading, I'll rename it.

I've removed the extra double quote. Thanks for the feedback.

Jim on 3 Sep 2018, 1:49 a.m.

how does this work with SVG that is loaded from a file? So in this example, the SVG is defined in the HTML document. I want to be able to load an SVG element from a file, and then scale, rotate and translate it across a specified area. I have tried adapting this code, but it hasn't worked so far. I think maybe I need a new approach?

Peter on 4 Sep 2018, 2:05 p.m.

I don't quite understand your set-up. Is the SVG an external file, which you're adding into the HTML using an <object>, <iframe> or <embed>? The code you be basically the same, but you might need to make a few changes depend on where the JS code is. I've cover the different ways to use JS and SVG here: http://www.petercollingridge.co.uk/tutorials/svg/interactive/javascript/ but let me know if it's not clear.

Christoph on 7 Sep 2018, 11:37 a.m.

Thank you very much for this fantastic post. I particular like the step-by-step approach, which really sets the basis for a good understanding. For me this has been the best post on this subject, which I was able to find so far.

Donnie on 9 Sep 2018, 2:54 a.m.

Have you successfully dragged a foreignObject? I keep getting NaN errors related to offset.

Peter on 10 Sep 2018, 10:14 p.m.

Hi Donnie, I added an example of dragging a foreignObject here: https://raw.githubusercontent.com/petercollingridge/code-for-blog/master/svg-interaction/draggable/draggable_foreign.svg

I added the draggable class to the foreign object element and then added a style foreignObject * { pointer-events: none; } so the mouse events ignored any of the other elements inside the foreignObject. I don't think you can transform them directly as non SVG elements won't have transform attributes.

Maja on 7 Nov 2018, 1:31 p.m.

I also just wanted to say thank you for this really useful tutorial. It saved me a lot of time having all of this on a silver platter.

Scott on 9 Jan 2019, 10:01 p.m.

Very clear presentation. Thanks for writing this!

binchem on 12 Jan 2019, 12:45 p.m.

add "transform: scale(0.5);" to SVG element, in Safari not work well. how can i fix it?

Billybobtimjim on 14 Feb 2019, 5:11 p.m.

I tried to do it on a svg sprite sheet and it didn't work.

Vip on 22 Feb 2019, 1:26 p.m.

Excellent tutorial. This was exactly what I was looking for. Thanks for making such evergreen content, so clearly written, and with great pedagogic design.

I've found that everything worked perfectly until I got to foreignObjects (no issue at all with your code for dragging, but one with scaling of fixed-size foreignObjects—the paragraph text was huge—that has a StackOverflow answer here: https://stackoverflow.com/questions/45043777/how-to-avoid-scaling-of-elements-inside-foreignobjects-of-svgs/45044646. I've improved the code that was there, if you want to include it/link to it: https://github.com/vipatron/fixed-size-foreignObject.js )

Also, one small error I noticed in the last file to which you linked via gitHub: You call getMousePosition, but have removed it from the makeDraggable function. (The code works perfectly when it is re-inserted).

Andrew on 25 Feb 2019, 9:53 a.m.

Hi there, this is awesome thank you. really simple clear and easy to follow.

Do you know how one object could be restricted to move in a sub region of the view Box. E.g. a smaller box to move the object within, inside the main box.

Thanks Andrew.

Christoff on 3 Mar 2019, 5:47 a.m.

Fantastic tutorial thank you!

I just want to know, is it in any way possible to restrict the draggable blocks so that they cannot exit the viewbox or to restrict them within the bounds of a div/container? Also, is there a way to "freeze"/disable the movement of the blocks when they reach a specific set of coordinates?

Thanks

George on 11 Apr 2019, 3:06 p.m.

First off thanks for a great tutorial. Seems like the complete code is not available in git at the link mentioned above (it is just a list of .svg files). I think it is probably in one the other directories so will look around. If not I can probably piece it together from the blog post but would be nice if there was a minimal downloadable working example..

George on 11 Apr 2019, 3:11 p.m.

I think I found it here in git: code-for-blog/svg-interaction/pan_and_zoom_mouse/test.html

Peter on 12 Apr 2019, 10:14 p.m.

Hi George, the code is in the SVG files. The SVGs contain a script element which contains everything.

John Geddes on 30 Apr 2019, 7:36 a.m.

Excellent stuff - thank you so much for sharing this. Everything was going so well with an interactive svg trial using your approach - until I tried using Edge on Windows 10 (version 44.17763.1.0, which is shown as current version at the date of writing).

As the code is written, dragging doesn't work properly on Edge, throwing these errors:

SCRIPT5022: IndexSizeError
SCRIPT5007: Unable to get property 'setTranslate' of undefined or null reference

BUT I did notice that the ellipse was OK. What was special about the ellipse? A quick check identified that this was the only svg element with a transform hard-coded into its original definition.

A bit of experimentation confirms that Edge is happy if every svg element that doesn't need a different transform nevertheless has transform='translate(0, 0)' added into its hard coding: there is something Edge doesn't like about the Javascript that adds such a transform automatically. There may be a more elegant solution, but this rescued me!

Davy on 15 Jul 2019, 10 p.m.

Hi @John Geddes,

I had a similar issue with Edge, fixed it with the following:
transforms.numberOfItems === 0

here:

if (transforms.length === 0 || transforms.numberOfItems === 0 || transforms.getItem(0).type !== SVGTransform.SVG_TRANSFORM_TRANSLATE) {

Hope this helps.

Peter2 on 13 Sep 2019, 1:10 p.m.

I checked the Z-ordering infos on the web.
Tried <use xlink:href=myid>, but lose the event handlers. Any lead?

James S. on 14 Sep 2019, 2:49 p.m.

I'm trying to decide whether or not to use the Pointer Lock API. It solves the issue of other elements getting mouse events during the drag operation, among other things. But it also seems to break touch compatibility and thus I must keep my existing code in some form in order to support touch dragging.
Any thoughts or insights on this, 7 years later?

Jiahuan on 16 Oct 2019, 5:59 a.m.

Hi there, I'm hitting an issue that onload does not seem to be supported by svg element.

Adam on 16 Nov 2019, 7:24 a.m.

Still very useful, thanks for the tutorial!!

Givanildo Silva de Amorim on 6 Dec 2019, 11:29 p.m.

Hello Mr Peter. Very cool tutorial! Thank you so much for the teachings! I would like to ask you for some guidance on the tooltip. How can I get the tooltip to work on mobile. I implemented the tooltip code from your tutorial in my svg and it worked normally, but dynamically created svg elements don't work. It is possible to give guidance on this. Thank you very much. Greetings from Brazil. Givanildo S. Amorim

Different Adam on 29 Dec 2019, 4:48 a.m.

Fantastic tutorial. Up until recently I only thought of SVG as that thing that icons and logos were designed in, but then I started looking into how stuff like draw.io are made and it turns out it was SVG. Mind blown. I'm now all in on taking SVG to its limits using React.

I'm curious of your thoughts on SVG vs WebGL. I looked into it some and it seems that WebGL is clearly more powerful but also SVGElement being a subclass of HTMLElement brings a lot to the table.

Marco Antonio on 13 Feb 2020, 6:49 a.m.

Thank you very much!! <3

Bart on 14 Feb 2020, 7:27 a.m.

One of the best-written tutorial I've ever seen. Thanks man, really appreciate it

Rodgers on 13 Mar 2020, 12:29 p.m.

Thanks very much for this excellent turoriel.
Can we do like the "sortable" function of jquery.
Cdlt,
Rodgers

AST on 26 Mar 2020, 9:34 a.m.

Your tutorial was perfect for the work I needed done. Thanks so much!

Bill W. on 18 Apr 2020, 3:34 p.m.

Outstanding work! Thank you very much for such a detailed explanation and example.

Joaquim on 13 May 2020, 7:20 p.m.

Can you give an example of droppable elements like a chessboard?

Bing Liu on 2 Jun 2020, 3:21 a.m.

Outstanding work! Thank you for sharing such a well-explained article.

Artem Eastwood on 6 Jun 2020, 10:08 p.m.

You are the best! It helped me a lot, thank you :)

Nali on 19 Jun 2020, 12:43 a.m.

So far the best article about dragging in SVG that I read from 2015 ! Great Job

karim abdel karim on 26 Jun 2020, 6:49 p.m.

i cant do this step "Fixing the coordinates" im getting this error

Uncaught ReferenceError: svg is not defined
at getMousePosition (WebForm2.aspx:47)
at SVGSVGElement.drag (WebForm2.aspx:36)

Danny Engelman on 21 Jul 2020, 2:52 p.m.

@Joaquim,
> Can you give an example of droppable elements like a chessboard?
There are SVG ChessPieces as Custom Elements on an interactive board at: https://chessmeister.github.io/

Colin on 19 Oct 2020, 8:57 p.m.

First off, this is excellent and exactly what I needed to get started with my 'learning' project. I'm creating a map where I can move objects around. The only piece I'm trying to add is a way to make the icons stick to every 10th pixel on both x and y when the user does a 'mouseup' to ensure that things remain lined up properly (otherwise there is a line of objects all off by 2 or 3 pixels and it drives me nuts). I've messed around a little with your code to try and make it work but with no success yet, any tips you can provide to even point me in the right direction would be appreciated!

Bjorn D on 9 Nov 2020, 10:40 p.m.

Pretty much an echo of the other comments here, but: Thanks a lot for this tutorial, it is extremely helpful! I'm currently making an SVG application using vue.js and most of the interaction is based on the techniques you have shown here. I'm planning to put out some codepens or similar with vue.js/SVG interaction. Vue.js and SVG is a really good match!