Dekel
Dekel

Reputation: 62636

SVG - animate/move dots along path

I'm looking for a way to move dots along existing path in order to get animation that looks like this:
enter image description here

I was thinking about working with dasharray but wasn't able to get this exact behavior.

Here is an example of something I tried, but as you can see it doesn't really work:

path.link {
  stroke-width: 3px;
  stroke-dasharray: 5 5;
  stroke: black;
}
path.link-anim {
  stroke-width: 3px;
  animation: link-anim 5s linear infinite;
}
path.red {
  stroke: red;
}
path.blue {
  stroke: blue;
}
path.green {
  stroke: green;
}
path.pink {
  stroke: pink;
}
@keyframes link-anim {
    0% {
        stroke-dashoffset: 0;
        stroke-dasharray: 5 5 100%;
    }
    100% {
        stroke-dashoffset: 100%;
        stroke-dasharray: 100% 5 5;
    }
}
<svg width="450" height="450">
  <g>
    <path class="link" d="M10,10L100,10"></path>
    <path class="link-anim red" d="M10,10L100,10"></path>
  </g>
  <g>
    <path class="link" d="M50,50L200,50"></path>
    <path class="link-anim blue" d="M50,50L200,50"></path>
  </g>
  <g>
    <path class="link" d="M75,75L75,200"></path>
    <path class="link-anim green" d="M75,75L75,200"></path>
  </g>
  <g>
    <path class="link" d="M85,85L450,450"></path>
    <path class="link-anim pink" d="M85,85L450,450"></path>
  </g>
</svg>

Note - the angle of the line is not something I care about. I should have written this from the start. What I need is the 3 dots to move forward (and only those 3 dots).

Upvotes: 5

Views: 6934

Answers (5)

Asons
Asons

Reputation: 87251

I added 2 more variants (sample 3, 4), using your original SVG and <animationTransform>.


Note 1, using <animationTransform> have less browser support

Note 2, since Chrome favor CSS/Web animations over SMIL, the future of SMIL might be somewhat unpredictable, so I would recommend to wait using it until its future is more secured.


Sample 1/2 benefit from rotating the element itself, which makes it much simpler when it comes to define each step, while with sample 3, one need to recalculate those for every angled used.

With sample 4 I made the 3 dots a solid line instead, using same color as the background, to show how that looks and its simpler code compared with sample 3.

Sample 1, using SVG

svg {
  background: black;
}
svg + svg {
  transform: rotate(45deg);
  transform-origin: left bottom;
}
line.nr2 {
  transform: translateX(-100%);
  animation: anim 2s steps(18) infinite;
}
@keyframes anim {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(500%); }
}
<svg width="320px" height="20px" viewBox="0 0 320 20">
    <line class="nr1" x1="10" x2="320" y1="10" y2="10" stroke="#f00" stroke-width="10" stroke-linecap="round" stroke-dasharray="1, 20"/>
    <line class="nr2" x1="10" x2="73" y1="10" y2="10" stroke="#fff" stroke-width="10" stroke-linecap="round" stroke-dasharray="1, 20"/>
</svg>

<svg width="320px" height="20px" viewBox="0 0 320 20">
    <line class="nr1" x1="10" x2="320" y1="10" y2="10" stroke="#f00" stroke-width="10" stroke-linecap="round" stroke-dasharray="1, 20"/>
    <line class="nr2" x1="10" x2="73" y1="10" y2="10" stroke="#fff" stroke-width="10" stroke-linecap="round" stroke-dasharray="1, 20"/>
</svg>


Sample 2, using pseudo element

div {
  position: relative;
  width: 180px;
  height: 55px;
  background: black;
  overflow: hidden;
  font-size:16px;
}
div + div {
  transform: rotate(45deg);
  transform-origin: left bottom;
}

div::before {
  content: '• • • • • • • • • • • • • • • • • • •';
  position: absolute;
  color: red;
  left: 0;
  top: 20px; 
}
div::after {
  content: '• • •';
  position: absolute;
  color: white;
  left: 2px;
  top: 20px;
  width: 40px;
  transform: translateX(-100%);
  animation: anim 2s steps(24) infinite;  
}

@keyframes anim {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(190px); }
}
<div></div>

<div></div>


Sample 3, 4

path.link {
  stroke-width: 3px;
  stroke-dasharray: 5 5;
  stroke: black;
}
path.red {
  stroke: red;
}
path.white {
  stroke: white;
  stroke-dasharray: 0;
}
<svg width="150" height="150">
  <g>
    <path class="link" d="M3,3L150,150"></path>
  </g>
  <g>
    <path id="path2" class="link anim red" d="M-18,-18L3,3">
      <animateTransform 
        attributeName="transform"
        type="translate"
        values="0 0;7 7;14 14;21 21;28 28;35 35;42 42;50 50;57 57;
                64 64;71 71;78 78;85 85;92 92;99 99;106 106;113 113;
                120 120;127 127;134 134;141 141;148 148;155 155"
        calcMode="discrete"
        begin="0s"
        dur="3s"
        repeatCount="indefinite"
      />
    </path>
  </g>
</svg>

<svg width="150" height="150">
  <g>
    <path class="link" d="M3,3L150,150"></path>
  </g>
  <g>
    <path id="path2" class="link anim white" d="M-18,-18L3,3">
      <animateTransform 
        attributeName="transform"
        type="translate"
        from="0 0"
        to="250 250"
        begin="0s"
        dur="3s"
        repeatCount="indefinite"
      />
    </path>
  </g>
</svg>

Upvotes: 2

Kaiido
Kaiido

Reputation: 137006

You can simply duplicate your paths, and set the stroke dash-array of the top one to only contain the 3 dots you need.

Then you can animate the dashoffset property of these top paths.

Note that it's not really modular, the dashoffset, dash-array and steps() timing function need to be calculated according to the path length.

path, circle {
  stroke-width: 4px;
  fill: transparent;
}

.bg {
  stroke: white;
  stroke-dasharray: 4 12;
}

.move {
  stroke: red;
  animation: link-anim 3s infinite steps(21);
  stroke-dasharray: 4 12 4 12 4 300;
}

@keyframes link-anim {
  0% {
    stroke-dashoffset: 368;
  }
  100% {
    stroke-dashoffset: 32;
  }
}

body {
  background: lightblue;
}
<svg width="500" height="330">
  <defs>
  <path id="v_path" d="M30,20V288"/>
  <path id="h_path" d="M30,20H358"/>
  <path id="d_path" d="M30,20L228 228"/>
  <circle id="c_path" cx="150" cy="150" r="53.4"/>
  </defs>
    <use class="bg" xlink:href="#v_path"/>
    <use class="move" xlink:href="#v_path"/>

    <use class="bg" xlink:href="#h_path"/>
    <use class="move" xlink:href="#h_path"/>

    <use class="bg" xlink:href="#d_path"/>
    <use class="move" xlink:href="#d_path"/>

    <use class="bg" xlink:href="#c_path"/>
    <use class="move" xlink:href="#c_path"/>

</svg>

Upvotes: 7

Frank Wisniewski
Frank Wisniewski

Reputation: 1224

I have made an Path animation with javascript (also running on IE) You can also animate your points in this way.

  <!DOCTYPE html>
  <html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <title>Nürburgring Animation</title>
    <meta name="author" content="Frank Wisniewski">
    <meta name="publisher" content="Frank Wisniewski">
    <meta name="copyright" content="Frank Wisniewski">
    <meta name="description" content="Auf dieser Seite wird eine kleine Animation des Nürburgrings um Adenau dargestellt. Die Animation wurde komplett in Javascript und SVG erstellt.">
    <meta name="keywords" content="SVG, Animation, Webdesign, Adenau, Nürburgring, Eifel, Frank, Wisniewski, Programmierung, Grafik, Gestaltung, Kunst">
    <meta name="page-topic" content="Forschung Technik">
    <meta name="page-type" content="Karte Plan">
    <meta name="audience" content="Alle, Erwachsene, Fans"><meta http-equiv="content-language" content="de">
    <meta name="robots" content="index, follow">
    <meta name="DC.Creator" content="Frank Wisniewski">
    <meta name="DC.Publisher" content="Frank Wisniewski">
    <meta name="DC.Rights" content="Frank Wisniewski">
    <meta name="DC.Description" content="Auf dieser Seite wird eine kleine Animation des Nürburgrings um Adenau dargestellt. Die Animation wurde komplett in Javascript und SVG rtstellt.">
    <meta name="DC.Language" content="de">

    
    <style>
      body{font-family:"Calibri", "Helvetica", sans-serif;}
      .content{width:300px;margin-left:auto;margin-right:auto;}
      #svgG{width:200px;margin-left:auto;margin-right:auto;}
      svg{width:200px;height:200px;overflow:hidden;}
      h1,p{text-align:center;}
      p{font-size:8px;}
      .p10{font-size:14px;font-weight:bold;}
    </style> 
  </head>
  <body>
    <div class="content">
    <div id="svgG"></div>
    <hr>
    <p>(c) Frank Wisniewski<br>Lohmühlenstraße 2</br>53518 Adenau</p>
    
    </div>
  <script>    
  /* Programm und Grafiken sind geistiges Eigentum von Frank Wisniewski, Lohmühlenstraße 2, 53518 Adenau und dürfen ohne Genehemigung nicht genutzt werden */

      var carCount = 8;
      var i;
      var colors = ["Black", "Navy", "Blue", "BlueViolet", "CornFlowerBlue","Red", "LimeGreen", "IndianRed", "Sienna"];
      var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
      var _create = function (type){return document.createElementNS("http://www.w3.org/2000/svg", type);}
      var _set = function (el,par){for (key in par) {el.setAttribute(key.replace('X','-'),par[key]);}svg.appendChild(el);}
      with(svg){ 
        setAttribute('width', '250');
        setAttribute('height', '250');
        setAttribute("viewBox", "0 0 250 250"); 
        setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
      } 
      svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
      svgG.appendChild(svg);
      var adenaubullet = _create("circle");
      _set(adenaubullet,{r:7,fill:'#f00',stroke:'#f00',cx:110,cy:8,strokeXwidth:1});
      var pfad = _create("path");
      pfad.setAttribute("d","M23.748,97.732c-4.235-2.956-3.875-3.774,0.149-6.424c4.415-2.912,9.246-4.657,13.445-7.991c3.775-2.997,6.616-6.559,8.157-11.154c0.658-1.969,0.469-3.708,0.928-5.656c0.49-2.073,2.818-3.084,2.289-5.231c-1.943-7.859,12.718-12.905,15.122-20.157c3.09-9.145-16.734-9.687-5.879-15.675c3.809-2.103,3.375-6.84,6.656-9.057c4.651-3.144,8.367,1.576,11.515,4.158c3.215,2.642,3.816-0.669,7.024-1.456c3.189-0.786,10.073,3.112,11.988-0.465c1.826-3.412,1.65-7.33,6.191-8.478c4.341-1.098,8.877-0.364,12.739-3.081c1.894-1.331,6.225-6.532,8.439-2.986c2.437,3.904-0.348,10.177,0.854,14.706c2.772,10.454,17.172,9.7,25.21,10.781c9.04,1.215,17.95,12.277,26.751,6.006c3.794-2.695,4.927-5.525,10.095-6.125c1.264-0.15,7.907-1.917,7.61,0.729c-0.316,2.853-6.305,5.333-8.338,6.711c-4.479,3.022-0.753,6.298,1.884,2.81c3.211-4.251,8.561-3.146,12.597-5.729c3.836-2.448,4.706-5.857,5.413-9.774c1.985-10.859,17.365-0.166,21.786,3.753c4.462,3.959,0.835,6.313,1.039,10.839c0.191,3.879,3.526,1.893,5.27,4.377c1.404,1.99,0.811,7.445-1.462,8.753c-3.792,2.168-6.586-2.64-9.273,2.805c-1.183,2.4-3.285,4.19-3.92,6.858c-0.735,3.091,1.969,7.785-0.122,10.477c-1.086,0.723-2.258,1.257-3.517,1.6c-1.907,1.228-3.592,3.089-5.258,4.608c-2.656,2.422-4.04,5.798-6.476,8.439c-3.986,4.336-10.271,6.805-16.063,7.526c-1.563,0.396-3.123,0.396-4.681,0c-2.271-0.944-1.521-3.241-4.827-2.771c-2.364,0.319-6.657,3.103-4.31,5.873c3.164,3.744,14.496,4.718,10.232,11.852c-4.087,6.844-13.375,9.795-19.857,13.7c-7.564,4.557-15.104,9.15-22.606,13.8c-7.321,4.53-16.081,8.639-22.572,14.298c-4.59,4.036-7.287,12.104-12.4,15.145c-2.264,1.35-1.265,4.361-4.132,5.722c-2.816,1.335-5.621-0.041-7.605-2.184c-0.949-1.027-2.493-4.413-4.188-2.536c-2.399,2.652-5.102,5.234-8.833,5.743c-3.432,0.466-6.206-1.944-9.507-1.572c-3.674,0.413-5.729-0.104-7.561-3.373c-1.255-2.242-3.559-3.075-5.613-4.396c-3.346-2.152-5.549-4.272-8.34-7.002c-5.066-4.96-13.847-9.133-14.34-17.222c-0.215-3.656,2.038-7.426,2.341-11.079c0.397-4.697-0.287-9.311-1.945-13.715c-1.431-3.801-3.489-7.284-4.994-11.039C29.209,99.368,26.926,99.928,23.748,97.732"); 
      _set(pfad,{fill:'none',stroke:'#666',strokeXwidth:6});
      var bordstein=pfad.cloneNode(true);
      _set(bordstein,{stroke:'#ddd',strokeXwidth:4});
      var grandprix = _create("path");
      grandprix.setAttribute("d","M32.246,245.125c-2.99-2.644,4.779-6.693,6.57-8.077c3.212-2.479,6.842-4.163,5.87-8.961c-0.891-4.404,1.944-8.48,3.929-12.302c1.225-2.359,2.457-4.719,3.468-7.177c1.085-2.64,0.846-3.498-1.781-4.92c-2.916-1.579-8.019-3.264-4.406-7.271c3.643-4.035,7.452-7.957,11.752-11.297c3.277-2.547,7.475-2.651,11.457-3.249c1.722-0.26,13.399-0.733,13.39-2.918c-0.015-3.294,6.497-2.777,8.654-3.153c1.517-0.266,5.267-1.056,5.688,1.308c0.445,2.497-1.268,3.257-2.928,4.664c-7.362,6.223-15.047,12.292-22.803,18.019c-0.534,0.395-8.754,5.805-8.457,3.525c0.423-3.213,5.225-8.695,1.275-11.084c-2.188-1.332-4.133-0.486-6.367,0.376c-1.574,0.607-5.299,1.734-6.084,3.411c-0.74,1.58,0.625,3.774,2.29,3.882c1.022,0.069,3.515-1.991,3.427,0.323c-0.094,2.511,0.521,5.18-0.138,7.646c-0.792,2.955-2.661,5.646-4.08,8.322c-0.9,1.696-3.023,4.913-2.296,7.02c0.89,2.563,4.673,2.549,5.777,4.88c1.078,2.279-4.431,3.46-5.976,4.184c-2.971,1.387-5.743,2.851-8.27,4.98c-1.822,1.534-3.684,3.438-5.079,5.381C35.841,244.419,34.963,247.523,32.246,245.125");
      _set(grandprix,{fill:'none', stroke:'#000', strokeXwidth:4});
      var grandprixbordstein=grandprix.cloneNode(true);
      _set(grandprixbordstein,{stroke:'#f00',strokeXwidth:3});
      var myText=_create("text");
      with (myText){
        setAttribute("font-family", "Arial, sans-serif");
        setAttribute("font-weight", "bold");
        setAttribute("font-style", "italic");
        setAttribute("text-anchor", "middle");
      }
      var myTextNode = document.createTextNode("");
      myText.appendChild(myTextNode);
      var nordschleife=myText.cloneNode(true);
      _set(nordschleife,{x:120,y:77,fontXsize:16});
      nordschleife.textContent="NORDSCHLEIFE";
      var grandprixtext=nordschleife.cloneNode(true);
      _set(grandprixtext,{x:110,y:220,fontXsize:16});
      grandprixtext.textContent="GRAND-PRIX";
      var grandprixtext1=nordschleife.cloneNode(true);
      _set(grandprixtext1,{x:110,y:240,fontXsize:16});
       grandprixtext1.textContent="STRECKE";
      var adenautext=nordschleife.cloneNode(true);
      _set(adenautext,{x:165,y:14,fontXsize:16});
      adenautext.textContent="ADENAU";
      var roundSquare=[];
      var mySquare=_create("rect");
      var roundBullet=[];
      var roundBulletText=[];
      for (i=1;i <= carCount;i++){
        
        roundBullet[i]=adenaubullet.cloneNode(true);
        _set(roundBullet[i],{r:5,strokeXwidth:0,fill:colors[i],stroke:colors[i],cx:180,cy:120+i*5*3});
        
        roundSquare[i]=mySquare.cloneNode(true);
        _set(roundSquare[i],{x:203,y:113+i*5*3,fill:'#ddd',stroke:'none',height:13,width:30});
        
        roundBulletText[i]=adenautext.cloneNode(true);
        _set(roundBulletText[i],{x:210,y:125+i*5*3,fontXsize:14,fontXstyle:'normal'});
        roundBulletText[i].textContent="0";
      }
      var lapsText=nordschleife.cloneNode(true);
      _set(lapsText,{x:217,y:122,fontXsize:14,fontXstyle:'normal'});
      lapsText.textContent="LAPS";
      var cars = [];
      var step=[];
      var laps=[];
      var len = pfad.getTotalLength();
      var speed = 1.5;
      var pos = [];
      var pt;
      var repeater;
      var lapPos = [];
      var carProto = _create("circle");
      with (carProto){
        setAttribute("r", 3);
        setAttribute("stroke-width", "1");
      }
      for(i = 1; i <= carCount; i++){
        cars[i]=carProto.cloneNode(true);
        pos[i]=i*5+750;
        laps[i]=0;
        lapPos[i]=750;
        pt=pfad.getPointAtLength(len/1000*pos[i])
        _set(cars[i],{cx:pt.x,cy:pt.y,stroke:colors[i],fill:colors[i]});
        step[i]=speed+Math.random()/1;
      }
      var animate=function(){
        for(i = 1; i <= carCount; i++){
          pos[i]+=step[i];
          lapPos[i]+=step[i];
          if(pos[i]>1000){
            pos[i]=1;
            step[i]=speed+Math.random()/2;  
          }
          if (lapPos[i]>1750){
            lapPos[i]=750;
            laps[i]++;
            roundBulletText[i].textContent=laps[i];
          }
          pt=pfad.getPointAtLength(len/1000*pos[i])
          cars[i].setAttribute("cx", pt.x);
          cars[i].setAttribute("cy", pt.y);
        }
        //repeater = requestAnimationFrame(animate);
        repeater = setTimeout(animate,1000/60);
      }
      animate();

  </script>
  </body>
  </html>

Upvotes: 5

Uzi
Uzi

Reputation: 2634

a <path> has a d attribute with x, y points in it.

all you have to do is loop over those points and set the current one to a circle's cx and cy

the points will need to be cleaned before.

have a look at the Demo

Upvotes: 0

Keith
Keith

Reputation: 4137

What you need to do is add a rotate in your path. I also changed some of your settings so you can see the line work:

https://jsfiddle.net/yex8n8nj/

<div>
<svg>
  <g>
    <path class="link" d="M120,0L0,-120" transform='rotate(45)'></path>
    <path class="link-anim blue" d="M120,0L0,-120" transform='rotate(45)' >
 </path>
  </g>
</svg>
</div>

path.link {
  stroke-width: 3px;
  stroke-dasharray: 5 5;
  stroke: black;
}
path.link-anim {
  stroke-width: 3px;
  animation: link-anim 5s linear infinite;
}
path.red {
  stroke: red;
}
path.blue {
  stroke: blue;
}
@keyframes link-anim {
    0% {
        stroke-dashoffset: 0;
        stroke-dasharray: 5 5 100%;
    }
    100% {
        stroke-dashoffset: 100%;
        stroke-dasharray: 100% 5 5;
    }
}
div {
  margin-left: 50px;
  width: 100%;
  text-align: center;
  float: right;
}

Upvotes: 0

Related Questions