Creative Coding with SVGs
zuubaDigital

Dragging Elements

Dragging Elements codepen practice page

DOM vs SVG coordinates

Dragging an SVG element is a relatively easy coding exercise with one complicating factor: The coordinate system of the SVG. For "normal" dom elements, all you really need to do is get the mouse position and place the element you want to drag accordingly.

If you want to drag svg elements, you’ll need to translate the mouse position to the svg coordinate system, and then position the svg element.

The Dragging Elements codepen javascript section has variables for the svg and the "draggableElement", which contains whatever graphic we want to drag around. Let's add a dragging variable which we'll set to false.

1 2 3 const svg = document.querySelector("svg"); const draggableElement = document.querySelector("#draggableElement"); let dragging = false;

We'll start off by creating an init function with the mouse events we'll need to drag the draggableElement.

1 2 3 4 5 6 7 8 9 const svg = document.querySelector("svg"); const draggableElement = document.querySelector("#draggableElement"); let dragging = false; function init() { draggableElement.addEventListener("mousedown", startDrag); svg.addEventListener("mousemove", drawDrag); svg.addEventListener("mouseup", stopDrag); svg.addEventListener("mouseout", stopDrag); };

As you can see, I've added a "mousedown" listener on the draggableElement element, and added "startDrag" callback (which we'll create in a second).

svg.addEventListener(“mousemove”, drag)

I've also added a "mousemove" listener, but on the svg, and not the draggableElement element that we want to drag. Why would't we have this listener on the draggableElement element as well? Think about it - if you’re dragging an element, that element will be moving with the mouse - the mouse cursor wont be moving across the element, which is what we need in order for mousemove event to fire. The mousemove event listener has "drawDrag" as a callback, which we'll define below.

The other event listeners in this method are the "mouseup" and "mouseout" events, which we'll also attach to the svg itself. Both of these events have a "stopDrag" callback.

Now let's add the startDrag and stopDrag methods:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const svg = document.querySelector("svg"); const draggableElement = document.querySelector("#draggableElement"); let dragging = false; function init() { draggableElement.addEventListener("mousedown", startDrag); svg.addEventListener("mousemove", drawDrag); svg.addEventListener("mouseup", stopDrag); svg.addEventListener("mouseout", stopDrag); }; function startDrag() { dragging = true; }; function stopDrag() { dragging = false; };

The startDrag and stopDrag methods are simple - they just set dragging to true or false. We'll use the dragging variable in our drawDrag function (defined below).

Now let's create our drawDrag method. Remember, this is the event handler method attached to the mousemove event we attached to the svg. Note that if dragging is false, nothing happens.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const svg = document.querySelector("svg"); const draggableElement = document.querySelector("#draggableElement"); let dragging = false; function init() { draggableElement.addEventListener("mousedown", startDrag); svg.addEventListener("mousemove", drawDrag); svg.addEventListener("mouseup", stopDrag); svg.addEventListener("mouseout", stopDrag); }; function startDrag() { dragging = true; }; function stopDrag() { dragging = false; }; function drawDrag(mouseEvent) { if (!dragging) return; const mouseX = mouseEvent.clientX; const mouseY = mouseEvent.clientY; };

Now that we have the page coordinates for the mouse point (mouseX, mouseY), we'll need to convert these coordinates to the coordinates on the SVG.

We do this by using our mouse coordinate to create something called a DOMPoint. A DOMPoint is one of the Web APIs provided by modern browsers, and it contains a matrixTransform method that will allow us to do what we need to do. How it works is a bit beyond the scope of this course - all you need to know is that it works! Here's a method I created that uses the DOMPoint to return our corresponding SVG coordinate:

function toSVGPoint(x, y, theSVG) {
  let p = new DOMPoint(x, y);
  return p.matrixTransform(theSVG.getScreenCTM().inverse());
};

Let's use this method in our drawDrag method:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const svg = document.querySelector("svg"); const draggableElement = document.querySelector("#draggableElement"); let dragging = false; function init() { draggableElement.addEventListener("mousedown", startDrag); svg.addEventListener("mousemove", drawDrag); svg.addEventListener("mouseup", stopDrag); svg.addEventListener("mouseout", stopDrag); }; function startDrag() { dragging = true; }; function stopDrag() { dragging = false; }; function drawDrag(mouseEvent) { if (!dragging) return; const svgPoint = toSVGPoint(mouseEvent.clientX, mouseEvent.clientY, svg); draggableElement.setAttribute("transform", `translate(${svgPoint.x} ${svgPoint.y})`); }; function toSVGPoint(x, y, theSVG) { let p = new DOMPoint(x, y); return p.matrixTransform(theSVG.getScreenCTM().inverse()); };

As you can see, all we're doing is passing our mouse x and y points to the toSVGPoint method to get our SVG coordinates,k then setting the transform attribute of our draggableElement to those coordinates.

Now that we have all our methods, all we need to do is call our init() method.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const svg = document.querySelector("svg"); const draggableElement = document.querySelector("#draggableElement"); let dragging = false; function init() { draggableElement.addEventListener("mousedown", startDrag); svg.addEventListener("mousemove", drawDrag); svg.addEventListener("mouseup", stopDrag); svg.addEventListener("mouseout", stopDrag); }; function startDrag() { dragging = true; }; function stopDrag() { dragging = false; }; function drawDrag(mouseEvent) { if (!dragging) return; const svgPoint = toSVGPoint(mouseEvent.clientX, mouseEvent.clientY, svg); draggableElement.setAttribute("transform", `translate(${svgPoint.x} ${svgPoint.y})`); }; function toSVGPoint(x, y, theSVG) { let p = new DOMPoint(x, y); return p.matrixTransform(theSVG.getScreenCTM().inverse()); }; init()

And that's it!

I've created another slightly more complex example where I create and position the draggable elements dynamically using the methods we studied in earlier lessons.

And that's it!