{"id":194,"date":"2005-12-21T12:18:13","date_gmt":"2005-12-21T18:18:13","guid":{"rendered":"http:\/\/blog.codedread.com\/?p=194"},"modified":"2005-12-21T12:18:13","modified_gmt":"2005-12-21T18:18:13","slug":"how-to-enable-dragging-in-svg","status":"publish","type":"post","link":"https:\/\/www.codedread.com\/blog\/archives\/2005\/12\/21\/how-to-enable-dragging-in-svg\/","title":{"rendered":"How To Enable Dragging in SVG"},"content":{"rendered":"<p><abbr title=\"Scalable Vector Graphics\">SVG<\/abbr> allows you to do quite a bit in terms of graphics in the browser that was not possible without a special plugin like Flash.  This post describes how I enabled dragging of entities around the screen.  <!--more--><\/p>\n<div class=\"ads\"><object type=\"text\/html\" width=\"468\" height=\"60\" data=\"http:\/\/www.codedread.com\/gads.php\"><\/object><\/div>\n<h2 id=\"intro\">Introduction<\/h2>\n<p>If you are unfamiliar with SVG I would recommend checking out my two Kickstart tutorials here:  <a href=\"http:\/\/www.codedread.com\/SVGKS_1a.php\">One<\/a> and <a href=\"http:\/\/www.codedread.com\/SVGKS_2a.php\">Two<\/a>.  Furthermore, you can see many useful examples at <a href=\"http:\/\/www.svgbasics.com\/\" title=\"SVGBasics.com\">SVGBasics<\/a>.  This should give you a good start if you've never seen SVG or XML before.  However, this entry also assumes you are moderately familiar with web development concepts like the DOM and JavaScript.<\/p>\n<p>My goal was to create an SVG document that allowed the user to drag entities around the screen as they do with existing windowing environments today.  The final results of my work can be seen <a href=\"http:\/\/www.codedread.com\/dragtest2.svg\">HERE<\/a>.<\/p>\n<h2 id=\"events\">SVG Events<\/h2>\n<p>In SVG 1.1, you can hook up event handlers to SVG entities by techniques that should be familiar to most HTML\/JS coders nowadays:<\/p>\n<div class=\"code\">\n<p>&#60;script&#62;&#60;![CDATA[<br \/>\nfunction buttonClick() {<br \/>\n&#160;&#160;alert('Hello, world!');<br \/>\n}<br \/>\n]]&#62;&#60;script&#62;<\/p>\n<p>&#60;rect id='button' onclick='buttonClick()' ... \/&#62;<\/p>\n<\/div>\n<p>When the rectangle is clicked on, the JavaScript function buttonClick() will be called which lets you do whatever you want.  In SVG 1.2 this will change as it uses only DOM Level 3 Events (introducing handlers and listeners), but since there will be some time before SVG 1.2 becomes the defacto standard to code for, SVG 1.1 is it.<\/p>\n<h2 id=\"dragging\">Dragging<\/h2>\n<p>Now that you understand how to hook up event handlers, the basics of a dragging operation are:<\/p>\n<ul>\n<li>User presses the mouse button while the cursor is on top of an object<\/li>\n<li>User moves the mouse while holding the button down (dragging), the object should follow the mouse cursor<\/li>\n<li>User lifts the mouse button to \"release\" the object<\/li>\n<\/ul>\n<p>Each of these is handled by three different events:  mousedown, mousemove and mouseup that you are able to capture and handle with arbitrary JavaScript.<\/p>\n<h2 id=\"mouse\">SVG Mouse Coordinates<\/h2>\n<p>The tricky part of handling mouse events in SVG comes with the fact that the client coordinates (i.e. the browser's window) does not necessarily equal the SVG user coordinates.  This is because in SVG you can change coordinate systems within your SVG document by specifying a <em>viewBox<\/em> attribute on the top-level <em>&#60;svg&#62;<\/em> element and you can transform SVG entities by scaling, translating, skewing, etc.  These are very convenient features to have, yet they do cause some hassle with mapping mouse coordinates to SVG coordinates.<\/p>\n<p>In my investigations, I started with <a href=\"http:\/\/groups.yahoo.com\/group\/svg-developers\/message\/52701\">this post<\/a> by Jonathan Watt on the Yahoo! svg-developers mailing list where he specifies:<\/p>\n<div class=\"code\">\nvar m = El.getScreenCTM();<br \/>\nvar p = document.documentElement.createSVGPoint();<br \/>\np.x = evt.clientX;<br \/>\np.y = evt.clientY;<br \/>\np = p.matrixTransform(m.inverse());\n<\/div>\n<p>This takes the <em>clientX<\/em> and <em>clientY<\/em> values within the event object (familiar to most HTML\/JavaScript coders nowadays) and transforms them using the inverse of the SVG element's Current Transformation Matrix (CTM).  What does that mean?  Well, it translates my mouse coordinates into SVG user coordinates for a given SVG element.  Exactly what I want.  With that we can determine how the mouse cursor is offset from a given anchor point within the SVG element and properly drag it around.<\/p>\n<p>I used the above technique in a very simple demo here: <a href=\"http:\/\/www.codedread.com\/dragtest.svg\">Draggable SVG Demo One<\/a>.   Supported platforms are Internet Explorer + Adobe SVG Viewer 6.0 Alpha and Firefox 1.5.  Unfortunately it looks like Opera 9 TP1 and ASV 3.0 do not yet support getScreenTCM() on its SVG elements.  Too bad, hopefully they will before Opera 9 is finally released.  Anyway, if you have one of the above two configurations, you should be able to drag around the circle to your heart's content.  What a blast...<\/p>\n<div class=\"ads\"><object type=\"text\/html\" width=\"468\" height=\"60\" data=\"http:\/\/www.codedread.com\/gads.php\"><\/object><\/div>\n<h2 id=\"useful\">Making It Useful<\/h2>\n<p>The problem came when I decided that I wanted to take this out of the 'simple demo phase' and into the 'practical use' phase.  The first demo (dragtest.svg) only works with one SVG entity (\"ball\") and that entity has to be a <em>&#60;circle&#62;<\/em> element, since the JavaScript modifies the <em>cx<\/em> and <em>cy<\/em> attributes when the mouse moves.  To make this useful outside of the laboratory, so to speak, requires that I generalize it for multiple entities and entity types.<\/p>\n<p>Since I might want to use dragging on arbitrarily shaped entities, I cannot use a technique that modifies <em>cx\/cy<\/em> attributes (this wouldn't work for <em>&#60;rect&#62;<\/em> or <em>&#60;g&#62;<\/em> entities).  I already briefly mentioned that SVG allows you to arbitrarily transform your entities via the <em>transform<\/em> attribute.  This is the general mechanism I decided to use to designate an entity's position:<\/p>\n<div class=\"code\">\n&#60;circle id='ball' transform='translate(400,300)' cx='0' cy='0' r='200' ...\/&#62;<br \/>\n&#60;rect id='box' transform='translate(500,500)' x='0' y='0' width='300' height='300' ... \/&#62;<br \/>\n&#60;g id='arbitrary_shape' transform='translate(1000,1000)' ... &#62; ... &#60;\/g&#62;\n<\/div>\n<p>I use a fixed shape (the \"background\" entity) to get the SVG document coordinate system and properly determine the mouse coordinates and offset within the shape, then I use the <em>transform<\/em> attribute to move the entities around.  The <em>transform<\/em> attribute allows me to do this to any shape without using entity-specific attributes like <em>cx<\/em> or <em>x<\/em>.  When I want to move the entity around I just do this:<\/p>\n<div class=\"code\">\nelement.setAttribute('transform', 'translate(' + p.x + ',' + p.y + ')');\n<\/div>\n<h2 id=\"DOMSticking\">DOM Sticking<\/h2>\n<p>But one issue did come up:  How to efficiently get the element's current position?  Looking at the above SVG code, it seems that I would have to get the value of the <em>transform<\/em> attribute (using the DOM getAttribute() method), then scan it looking for the two strings within the parentheses separated by a comma (or space), then parse those values into integers.  What a pain.<\/p>\n<p>So instead I decided to use a technique that I'm really growing to love in SVG DOM programming that I'll dub \"DOM Sticking\".  It involves embedding arbitrary bits of information into the SVG entities themselves.  It's best illustrated by modifying the above example SVG code:<\/p>\n<div class=\"code\">\n&#60;circle id='ball' transform='translate(400,300)' dragx='400' dragy='300' cx='0' cy='0' r='200' ...\/&#62;<br \/>\n&#60;rect id='box' transform='translate(500,500)' dragx='500' dragy='500' x='0' y='0' width='300' height='300' ... \/&#62;<br \/>\n&#60;g id='arbitrary_shape' transform='translate(1000,1000)' dragx='1000' dragy='1000' ... &#62; ... &#60;\/g&#62;\n<\/div>\n<p>In the above, I simply added the <em>dragx<\/em> and <em>dragy<\/em> attributes which are attributes invented by me that mean nothing to an SVG renderer.  In other words, the software drawing the picture completely ignores these attributes, but I can use them in my DOM to ease matters.  When I introduced these attributes, I made sure the <em>dragx\/dragy<\/em> attributes have the same initial values as the <em>transform<\/em> translation.<\/p>\n<p>Now whenever I move the elements, I need to update the <em>dragx\/dragy<\/em> attributes at the same time:<\/p>\n<div class=\"code\">\nelement.setAttribute('transform', 'translate(' + p.x + ',' + p.y + ')');<br \/>\nelement.setAttribute('dragx', p.x);<br \/>\nelement.setAttribute('dragy', p.y);\n<\/div>\n<p>But the benefit is that, since the <em>dragx\/dragy<\/em> attributes are always in sync with the element's current translation, when I want to determine the SVG entity's current position I can just use:<\/p>\n<div class=\"code\">\nvar x = parseInt(element.getAttribute('dragx'));<br \/>\nvar y = parseInt(element.getAttribute('dragy'));\n<\/div>\n<p>No complicated and expensive scanning of the <em>transform<\/em> attribute, no delving into the element's CTM, just pick them up from the DOM and use them.<\/p>\n<p>In using the above technique, it may be more proper to stick the <em>dragx\/dragy<\/em> attributes into their own namespace (in which case I'd have to use getAttributeNS and setAttributeNS methods), but for the purposes of this demo it seems to work just fine.  The reason for this is maybe because the SVG elements are in the SVG namespace but the attributes on those elements are in the null namespace.<\/p>\n<h2 id=\"asv3\">Enabling ASV3<\/h2>\n<p>Subsequent to my first publishing this entry, I was informed that ASV 3 doesn't support getScreenCTM() and someone had already posted a function that would calculate this manually.  Luckily I was able to find <a href=\"http:\/\/groups.yahoo.com\/group\/svg-developers\/message\/50789\">this function<\/a> from <a href=\"http:\/\/www.treebuilder.de\/\">Holger Will<\/a> and with a small correction to his function to handle percentages in width\/height - the function now works beautifully in ASV3.<\/p>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>You can see the final demo which allows you to drag around four objects here: <a href=\"http:\/\/www.codedread.com\/dragtest2.svg\">Draggable SVG Demo Two<\/a>.  It supports IE+ASV3, IE+ASV6, and Firefox 1.5.  Feel free to view the source and learn whatever you can.  By the way, I came across <a href=\"http:\/\/www.tecknik.net\/poke\/post.php\/118\">this blog entry<\/a> today so I thought I'd pick up his cool SVG feed icon and use it in my demo.  Since Firefox doesn't allow me to specify an external SVG document, I embedded his SVG into my own code.  Thanks, Marcus!<\/p>\n<p>To make this demo perfectly reusable, I would probably change the following:<\/p>\n<ul>\n<li>Use a <em>class<\/em> attribute to specify that an SVG entity is draggable.  i.e. &#60;circle id='ball' class='draggable' ...&#62;<\/li>\n<li>Then, remove the <em>dragx\/dragy<\/em> attributes from my SVG code.<\/li>\n<li>Then, in my JavaScript <em>init()<\/em> function, scan for all SVG entities of the class 'draggable' and in addition to adding the event listener, I would parse their <em>transform<\/em> attributes to get their initial position, then add the <em>dragx\/dragy<\/em> attributes from within the JavaScript at that stage.<\/li>\n<\/ul>\n<p>The above changes would have the effect of allowing you to easily turn entities into draggable entities by simply adding <em>class='draggable'<\/em> to the SVG description - no other changes would be required but you still gain the convenience and speed of DOM Sticking.<\/p>\n<div class=\"ads\"><object type=\"text\/html\" width=\"468\" height=\"60\" data=\"http:\/\/www.codedread.com\/gads.php\"><\/object><\/div>\n","protected":false},"excerpt":{"rendered":"<p>SVG allows you to do quite a bit in terms of graphics in the browser that was not possible without a special plugin like Flash. This post describes how I enabled dragging of entities around the screen.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[38,25,46,11,28,30],"tags":[],"class_list":["post-194","post","type-post","status-publish","format-standard","hentry","category-javascript","category-software","category-svg","category-technology","category-web","category-xml"],"_links":{"self":[{"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/posts\/194","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/comments?post=194"}],"version-history":[{"count":0,"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/posts\/194\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/media?parent=194"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/categories?post=194"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.codedread.com\/blog\/wp-json\/wp\/v2\/tags?post=194"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}