{"id":457,"date":"2020-03-22T11:23:19","date_gmt":"2020-03-22T11:23:19","guid":{"rendered":"https:\/\/potatodie.nl\/diffuse-write-ups\/?p=457"},"modified":"2021-02-11T08:46:47","modified_gmt":"2021-02-11T08:46:47","slug":"move-a-dot-along-a-path","status":"publish","type":"post","link":"https:\/\/potatodie.nl\/diffuse-write-ups\/move-a-dot-along-a-path\/","title":{"rendered":"Move a dot along a path"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">This post shows how to create a dot moving along a path using SVG and JavaScript (ES2015). In this case it&#8217;s a dot, but of course it could be anything.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Create graphics with Inkscape<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s create an image consisting of a curve and an object that we want to move along that curve. I&#8217;m using Inkscape as SVG editor, but you can use Adobe Illustrator as well, or even create the SVG file manually.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"841\" height=\"517\" src=\"https:\/\/potatodie.nl\/diffuse-write-ups\/wp-content\/uploads\/2020\/03\/dot-on-bumpy-path.png\" alt=\"\" class=\"wp-image-458\" srcset=\"https:\/\/potatodie.nl\/diffuse-write-ups\/wp-content\/uploads\/2020\/03\/dot-on-bumpy-path.png 841w, https:\/\/potatodie.nl\/diffuse-write-ups\/wp-content\/uploads\/2020\/03\/dot-on-bumpy-path-300x184.png 300w, https:\/\/potatodie.nl\/diffuse-write-ups\/wp-content\/uploads\/2020\/03\/dot-on-bumpy-path-768x472.png 768w\" sizes=\"auto, (max-width: 841px) 100vw, 841px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The image shows the two elements we need. The curly curve is given the id <em>curve<\/em>, the dot has id <em>dot<\/em>. Note the position of the dot: it&#8217;s placed on the origin.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The (cleaned-up) code for that graphic would be something like this:<\/p>\n\n\n<div class=\"wp-block-advanced-gutenberg-blocks-code\">\n  <header class=\"wp-block-advanced-gutenberg-blocks-code__header\">\n    <div class=\"wp-block-advanced-gutenberg-blocks-code__lang is-lang-html\">\n      HTML    <\/div>\n    <div class=\"wp-block-advanced-gutenberg-blocks-code__file\">\n          <\/div>\n  <\/header>\n  <textarea \n    class=\"wp-block-advanced-gutenberg-blocks-code__source\" \n    name=\"codemirror-674795033\" \n    id=\"codemirror-674795033\"\n  >&lt;svg\n   xmlns=&quot;http:\/\/www.w3.org\/2000\/svg&quot;\n   viewBox=&quot;0 0 700 200&quot;&gt;\n  &lt;path\n     id=&quot;curve&quot;\n     d=&quot;M 29.928125,119.32884 C 59.868756,78.85387 99.789596,30.283895 138.66049,30.104515 c 80.46172,-0.37133 90.87185,137.794285 140.7729,129.699295 40.35037,-6.54568 49.90105,-80.94994 86.53937,-78.86548 33.24073,1.89117 22.2547,70.77048 73.14399,70.77048 39.92084,0 58.65264,-56.66496 99.80211,-56.66496 49.90105,0 69.86147,80.94995 99.8021,48.56997&quot; \n     style=&quot;fill: none; stroke: #000; stroke-width: 1&quot; \/&gt;\n  &lt;circle\n     r=&quot;10&quot;\n     cy=&quot;0&quot;\n     cx=&quot;0&quot;\n     id=&quot;dot&quot;\n     style=&quot;fill: #dd1819&quot; \/&gt;\n&lt;\/svg&gt;<\/textarea>\n  <script>\n    CodeMirror.fromTextArea( document.getElementById('codemirror-674795033'), {\n      mode: 'xml',\n      readOnly: true,\n      theme: 'hopscotch', \n      lineNumbers: false,\n      firstLineNumber: 1,\n      matchBrackets: true,\n      indentUnit: 4,\n      tabSize: 4,\n      lineWrapping: false,\n    } ); \n  <\/script>\n<\/div>\n\n\n\n<h4 class=\"wp-block-heading\">Beware of group transforms<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">For more complicated graphics you will likely group elements or separate them in layers (which are actually groups too). In that case make sure you don&#8217;t have a transform applied to the group, to avoid problems addressed below. A simple trick to do this, is to ungroup and regroup the group. This transfers the transform data to the geometric elements inside the group.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Add graphics to HTML page<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Simply paste the SVG element in the body of the HTML document, or, if using PHP, include the SVG file.<\/p>\n\n\n<div class=\"wp-block-advanced-gutenberg-blocks-code\">\n  <header class=\"wp-block-advanced-gutenberg-blocks-code__header\">\n    <div class=\"wp-block-advanced-gutenberg-blocks-code__lang is-lang-php\">\n      PHP    <\/div>\n    <div class=\"wp-block-advanced-gutenberg-blocks-code__file\">\n          <\/div>\n  <\/header>\n  <textarea \n    class=\"wp-block-advanced-gutenberg-blocks-code__source\" \n    name=\"codemirror-1917514551\" \n    id=\"codemirror-1917514551\"\n  >&lt;?php include &#039;dot-on-bumpy-path.svg&#039;; ?&gt;<\/textarea>\n  <script>\n    CodeMirror.fromTextArea( document.getElementById('codemirror-1917514551'), {\n      mode: 'php',\n      readOnly: true,\n      theme: 'hopscotch', \n      lineNumbers: false,\n      firstLineNumber: 1,\n      matchBrackets: true,\n      indentUnit: 4,\n      tabSize: 4,\n      lineWrapping: true,\n    } ); \n  <\/script>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">Put the dot on the spot<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If we want to move the dot along the curve, obviously we first have to find out how to position the dot on a particular spot. To do this we&#8217;ll use two methods for SVG geometry:  <em>getTotalLength<\/em> and  <em>getPointAtLength<\/em>. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\"> <em>getTotalLength<\/em>  measures the total length of the path, while  <em>getPointAtLength<\/em> gives the position on the path given the distance from the starting point. More on <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/API\/SVGGeometryElement\">SVGGeometryElements<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s use a parameter <em>u <\/em>running from 0 to 1. When <em>u<\/em> = 0, the dot is on the starting point, while <em>u<\/em> = 1 corresponds with the end point. To put the dot half way we could write this:<\/p>\n\n\n<div class=\"wp-block-advanced-gutenberg-blocks-code\">\n  <header class=\"wp-block-advanced-gutenberg-blocks-code__header\">\n    <div class=\"wp-block-advanced-gutenberg-blocks-code__lang is-lang-js\">\n      JS    <\/div>\n    <div class=\"wp-block-advanced-gutenberg-blocks-code__file\">\n          <\/div>\n  <\/header>\n  <textarea \n    class=\"wp-block-advanced-gutenberg-blocks-code__source\" \n    name=\"codemirror-132790907\" \n    id=\"codemirror-132790907\"\n  >window.onload = () =&gt; {\n    let dot = document.getElementById(&#039;dot&#039;);\n    let curve = document.getElementById(&#039;curve&#039;);\n    let totalLength = curve.getTotalLength();\n\n    let u = 0.5;\n    let p = curve.getPointAtLength(u * totalLength);\n    \n    dot.setAttribute(&quot;transform&quot;, `translate(${p.x}, ${p.y})`);\n}<\/textarea>\n  <script>\n    CodeMirror.fromTextArea( document.getElementById('codemirror-132790907'), {\n      mode: 'javascript',\n      readOnly: true,\n      theme: 'hopscotch', \n      lineNumbers: false,\n      firstLineNumber: 1,\n      matchBrackets: true,\n      indentUnit: 4,\n      tabSize: 4,\n      lineWrapping: true,\n    } ); \n  <\/script>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">The following image shows the result in the browser window.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1002\" height=\"342\" src=\"https:\/\/potatodie.nl\/diffuse-write-ups\/wp-content\/uploads\/2020\/03\/dothalfway.png\" alt=\"\" class=\"wp-image-462\" srcset=\"https:\/\/potatodie.nl\/diffuse-write-ups\/wp-content\/uploads\/2020\/03\/dothalfway.png 1002w, https:\/\/potatodie.nl\/diffuse-write-ups\/wp-content\/uploads\/2020\/03\/dothalfway-300x102.png 300w, https:\/\/potatodie.nl\/diffuse-write-ups\/wp-content\/uploads\/2020\/03\/dothalfway-768x262.png 768w\" sizes=\"auto, (max-width: 1002px) 100vw, 1002px\" \/><\/figure>\n\n\n\n<h4 class=\"wp-block-heading\">Group transform issue<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">Suppose we organized our SVG file and added the curve in a group or layer. In that case  <em>getPointAtLength<\/em>  would not take any transforms applied to that group into account, so translating the dot based on the outcome of  <em>getPointAtLength<\/em>  would probably put the dot on the wrong place. Therefor we warned above to avoid transforms on groups or layers in the SVG document.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Move it<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s encapsulate the code for <em>dot<\/em> in an object, so we can repeatedly move the dot, without making a mess of the code.<\/p>\n\n\n<div class=\"wp-block-advanced-gutenberg-blocks-code\">\n  <header class=\"wp-block-advanced-gutenberg-blocks-code__header\">\n    <div class=\"wp-block-advanced-gutenberg-blocks-code__lang is-lang-js\">\n      JS    <\/div>\n    <div class=\"wp-block-advanced-gutenberg-blocks-code__file\">\n          <\/div>\n  <\/header>\n  <textarea \n    class=\"wp-block-advanced-gutenberg-blocks-code__source\" \n    name=\"codemirror-2111767653\" \n    id=\"codemirror-2111767653\"\n  >const dot = {\n    sprite: null,\n    track: null,\n\n    \/\/ Initialize the dot: connect sprite and track properties with supplied SVG elements\n    init: function(sprite, track) {\n        this.sprite = document.getElementById(sprite);\n        this.track = document.getElementById(track);\n    },\n    \n    \/\/ Put the dot on its spot\n    move: function(u) {\n        const p = this.track.getPointAtLength(u * this.track.getTotalLength());\n        this.sprite.setAttribute(&quot;transform&quot;, `translate(${p.x}, ${p.y})`);\n    }\n};<\/textarea>\n  <script>\n    CodeMirror.fromTextArea( document.getElementById('codemirror-2111767653'), {\n      mode: 'javascript',\n      readOnly: true,\n      theme: 'hopscotch', \n      lineNumbers: false,\n      firstLineNumber: 1,\n      matchBrackets: true,\n      indentUnit: 4,\n      tabSize: 4,\n      lineWrapping: false,\n    } ); \n  <\/script>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">To control the animation, let&#8217;s create another object, <em>anim,<\/em> that can be started specifying the duration of the animation. This demo restarts the animation a second after it&#8217;s finished.<\/p>\n\n\n<div class=\"wp-block-advanced-gutenberg-blocks-code\">\n  <header class=\"wp-block-advanced-gutenberg-blocks-code__header\">\n    <div class=\"wp-block-advanced-gutenberg-blocks-code__lang is-lang-js\">\n      JS    <\/div>\n    <div class=\"wp-block-advanced-gutenberg-blocks-code__file\">\n          <\/div>\n  <\/header>\n  <textarea \n    class=\"wp-block-advanced-gutenberg-blocks-code__source\" \n    name=\"codemirror-1883262581\" \n    id=\"codemirror-1883262581\"\n  >const anim = {\n    start: function(duration) {\n        this.duration = duration;\n        this.tZero = Date.now();\n\n        requestAnimationFrame(() =&gt; this.run());\n    },\n    \n    run: function() {\n        let u = Math.min((Date.now() - this.tZero) \/ this.duration, 1);\n        \n        if (u &lt; 1) {\n            \/\/ Keep requesting frames, till animation is ready\n            requestAnimationFrame(() =&gt; this.run());\n        } else {\n            this.onFinish();\n        }\n        \n        dot.move(u);\n    },\n    \n    onFinish: function() {\n        \/\/ Schedule the animation to restart\n        setTimeout(() =&gt; this.start(this.duration), 1000);\n    }\n};<\/textarea>\n  <script>\n    CodeMirror.fromTextArea( document.getElementById('codemirror-1883262581'), {\n      mode: 'javascript',\n      readOnly: true,\n      theme: 'hopscotch', \n      lineNumbers: true,\n      firstLineNumber: 1,\n      matchBrackets: true,\n      indentUnit: 4,\n      tabSize: 4,\n      lineWrapping: false,\n    } ).markText( {line: 9, ch: 0}, {line: 10, ch: 0}, {className: 'CodeMirror-styled-background'} );; \n  <\/script>\n<\/div>\n\n\n\n<p class=\"wp-block-paragraph\">In the function <em>run<\/em> (line 10 in the code above) we calculated the progress parameter <em>u<\/em>, by dividing the time the animation is running by the total running time. This expresses the progress of the animation on a scale from 0 to 1. If we are beyond the time set as duration we finish the animation by limiting <em>u<\/em> to 1.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">When the DOM is ready, just intitialise the dot, and start the animation:<\/p>\n\n\n<div class=\"wp-block-advanced-gutenberg-blocks-code\">\n  <header class=\"wp-block-advanced-gutenberg-blocks-code__header\">\n    <div class=\"wp-block-advanced-gutenberg-blocks-code__lang is-lang-js\">\n      JS    <\/div>\n    <div class=\"wp-block-advanced-gutenberg-blocks-code__file\">\n          <\/div>\n  <\/header>\n  <textarea \n    class=\"wp-block-advanced-gutenberg-blocks-code__source\" \n    name=\"codemirror-1659602386\" \n    id=\"codemirror-1659602386\"\n  >window.onload = () =&gt; {   \n    dot.init(&#039;dot&#039;, &#039;curve&#039;);\n    anim.start(2000);\n}<\/textarea>\n  <script>\n    CodeMirror.fromTextArea( document.getElementById('codemirror-1659602386'), {\n      mode: 'javascript',\n      readOnly: true,\n      theme: 'hopscotch', \n      lineNumbers: false,\n      firstLineNumber: 1,\n      matchBrackets: true,\n      indentUnit: 4,\n      tabSize: 4,\n      lineWrapping: false,\n    } ); \n  <\/script>\n<\/div>\n\n\n\n<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" viewBox=\"0 0 700 200\">\n  <path id=\"curve\" d=\"M 29.928125,119.32884 C 59.868756,78.85387 99.789596,30.283895 138.66049,30.104515 c 80.46172,-0.37133 90.87185,137.794285 140.7729,129.699295 40.35037,-6.54568 49.90105,-80.94994 86.53937,-78.86548 33.24073,1.89117 22.2547,70.77048 73.14399,70.77048 39.92084,0 58.65264,-56.66496 99.80211,-56.66496 49.90105,0 69.86147,80.94995 99.8021,48.56997\" style=\"fill: none; stroke: #000; stroke-width: 1\"><\/path>\n  <circle r=\"10\" cy=\"0\" cx=\"0\" id=\"dot\" style=\"fill: #dd1819\"><\/circle>\n<\/svg>\n\n\n\n<h2 class=\"wp-block-heading\">Repo<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Source and transpiled code at <a href=\"https:\/\/github.com\/potatoDie\/move-dot\">github<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Add timing function<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The follow-up post <a href=\"https:\/\/potatodie.nl\/diffuse-write-ups\/move-eased-dot-along-a-curve\/\">Ease a dot along a curve<\/a> demonstrates the effect of easing (or timing) functions to an animation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pause, resume<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">There&#8217;s also a <a href=\"https:\/\/codesandbox.io\/s\/move-dot-along-a-curve-uvbx6\">variant <\/a>available on Codesandbox that lets you pause and resume the animation on <code>mouseover <\/code>and  <code>mouseleave<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Create a dot moving along a path using SVG and JavaScript<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[22,20],"tags":[32,15],"class_list":["post-457","post","type-post","status-publish","format-standard","hentry","category-animation","category-javascript","tag-inkscape","tag-svg"],"acf":[],"_links":{"self":[{"href":"https:\/\/potatodie.nl\/diffuse-write-ups\/wp-json\/wp\/v2\/posts\/457","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/potatodie.nl\/diffuse-write-ups\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/potatodie.nl\/diffuse-write-ups\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/potatodie.nl\/diffuse-write-ups\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/potatodie.nl\/diffuse-write-ups\/wp-json\/wp\/v2\/comments?post=457"}],"version-history":[{"count":30,"href":"https:\/\/potatodie.nl\/diffuse-write-ups\/wp-json\/wp\/v2\/posts\/457\/revisions"}],"predecessor-version":[{"id":940,"href":"https:\/\/potatodie.nl\/diffuse-write-ups\/wp-json\/wp\/v2\/posts\/457\/revisions\/940"}],"wp:attachment":[{"href":"https:\/\/potatodie.nl\/diffuse-write-ups\/wp-json\/wp\/v2\/media?parent=457"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/potatodie.nl\/diffuse-write-ups\/wp-json\/wp\/v2\/categories?post=457"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/potatodie.nl\/diffuse-write-ups\/wp-json\/wp\/v2\/tags?post=457"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}