SVG - animate/move 沿路径的点

SVG - animate/move dots along path

我正在寻找一种方法来沿现有路径移动点以获得如下所示的动画:

我正在考虑使用 dasharray 但无法获得这种确切的行为。

这是我尝试过的一个例子,但如您所见,它并没有真正起作用:

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

您需要做的是在您的路径中添加一个旋转。我还更改了您的一些设置,以便您可以看到该行:

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;
}

a <path> 有一个 d 属性,其中有 x, y 点。

您所要做的就是遍历这些点并将当前点设置为 circlecxcy

之前需要清理点。

看看Demo

您可以简单地复制您的路径,并将顶部路径的笔划破折号数组设置为仅包含您需要的 3 个点。

然后您可以为这些顶部路径的 dashoffset 属性 设置动画。

注意不是真正的模块化,dashoffset、dash-array和steps()定时函数需要根据路径长度计算

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>

我用javascript做了一个路径动画(在IE上也是运行)你也可以 以这种方式为您的点设置动画。

  <!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>

我使用您的原始 SVG 和 <animationTransform> 添加了另外 2 个变体(示例 3、4)。


注1,使用<animationTransform>浏览器支持较少

注2,由于Chrome favor CSS/Web animations over SMIL,SMIL的未来可能有些不可预测,所以我建议等到它的未来更安全后再使用它。


样本 1/2 受益于旋转元素本身,这使得定义每个步骤时变得更加简单,而对于样本 3,需要为每个使用的角度重新计算。

对于示例 4,我将 3 个点改为实线,使用与背景相同的颜色,以显示它的外观以及与示例 3 相比其更简单的代码。

示例 1,使用 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>


示例2,使用伪元素

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>


示例 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>