Projectile motion on a path in SVG

214 Views Asked by At

In this SVG below, a circle moves on the given path linearly at a constant speed. I want it to move according to the curve (like a projectile, since this curve is a parabola). I have read about keySplines, keyPoints and keyTimes but cannot apply them correctly to obtain a smooth projectile motion. Is there a way to achieve this?

<svg viewBox="0 0 500 500" width="300px">
  <path fill="none" stroke="black" d="M250,250 Q356.06601717798213,143.93398282201787 462.13203435596427,487.86796564403573"> 
  </path>
  <circle r="5" fill="red">
    <animateMotion dur="5s" path="M250,250 Q356.06601717798213,143.93398282201787 462.13203435596427,487.86796564403573" fill="freeze">
    </animateMotion>
  </circle>
</svg>

3

There are 3 best solutions below

2
JMP On

You need to use keyPoints. I added an <mpath> element because they are easier to maintain.

<svg viewBox="0 0 500 500" width="300px">
  <path fill="none" stroke="black" id="motionPath" d="M250,250 Q356.06601717798213,143.93398282201787 462.13203435596427,487.86796564403573"/>
  <circle r="5" fill="red">
    <animateMotion dur="5s" keyPoints="0;0.22;0.3;1" keyTimes="0;0.45;0.55;1" calcMode="linear" fill="freeze">
      <mpath xlink:href="#motionPath"/>
    </animateMotion>
  </circle>
</svg>

3
Alexandr_TT On

I want it to move according to the curve (like a projectile, since this curve is a parabola).

I changed the motion path to look like a parabola.
Using the attributes keyPoints="0;0.4;0.6;1" and keyTimes="0;0.3;0.7;1" made the movement of the projectile along the trajectory uneven:
The projectile moves quickly to the top of the parabola In the upper part, the projectile slows down and accelerates again on the descent

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="svg4" width="500" height="500" viewBox="0 250 500 500">

 <path id="trace" d="M47.9 460.6C120.2 238.4 380 226.7 450.7 456"  fill="none" stroke="#000" stroke-dasharray="5 5"/>
  
 <circle cx="0" cy="0" r="5" fill="red" >
  <animateMotion   begin="0s" dur="3s"  rotate="auto" 
    keyPoints="0;0.4;0.6;1"
    keyTimes="0;0.3;0.7;1"
    calcMode="linear"
    repeatCount="5">
      <mpath xlink:href="#trace"/>
    </animateMotion>  
</circle>
</svg>

The example below is a bit more realistic. A projectile is drawn that flies out of a cannon barrel. During the shot, the gun rolls back and returns. The animation starts after the click.

enter image description here

<svg id="svg1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="svg4" width="1000" height="1000" viewBox="0 150 500 500">
<defs>
  <linearGradient id="Lg1">
     <stop offset="0%" stop-color="white" /> 
       <stop offset="100%" stop-color="black" />
   </linearGradient > 
     <linearGradient id="Lg2" x1="0" y1="0" y2="1">
     <stop offset="10%" stop-color="#D8BB00" /> 
       <stop offset="100%" stop-color="#685A00" />
   </linearGradient > 
  <linearGradient id="Lg3" x1="0" x2="0" y1="0" y2="1">
     <stop offset="80%" stop-color="dodgerblue" stop-opacity="0.8"/> 
       <stop offset="95%" stop-color="green" />
         <stop offset="100%" stop-color="brown" />
   </linearGradient >  
</defs>  
  <rect width="100%" height="100%" fill="url(#Lg3)" />
<path id="trace" d="M47.9 460.6C120.2 238.4 380 226.7 450.7 456"  fill="none" stroke-width="0.8" stroke="#000" stroke-dasharray="5 5"/>
<g id="cannon">
  <path id="wheel" d="M59.2 475.4c0 10-7 18.1-15.7 18.1-8.6 0-15.6-8-15.6-18s7-18.2 15.6-18.2c8.7 0 15.7 8.1 15.7 18.1z" opacity="1" fill="none" fill-opacity="1" stroke="#414141" stroke-width="2"  stroke-miterlimit="4"  /> 
 
  <path id="barrel" fill="url(#Lg1)" d="M54.3 434.3 39 478.5h6.2l14.6-41.3z"   stroke="none" stroke-width="1"  />
    <g stroke-width="3" stroke="#000" opacity="0.8">
   <path d="M26 479.4h29.4" id="path848" />
  <path d="M40.6 462.3v33.9" id="path850"  />
  <path d="M30.3 491.4 51 467" id="path852"  />
  <path d="m30.4 467.1 20.9 24.5" id="path854"  /> 
    </g>
  <path id="wheel" d="M56.3 479.2c0 10-7 18-15.6 18s-15.6-8-15.6-18S32 461 40.7 461c8.7 0 15.6 8 15.6 18z"  opacity="1" fill="#8B8B8B" fill-opacity="0.5" stroke="#414141" stroke-width="2"  stroke-miterlimit="4"  />

  <path id="lafet" d="m40.6 479.4-16.6 16-18.7.2" fill="none" stroke="#2C2C2C" stroke-width="5" stroke-linecap="round" />
  </g>
 <g  id="projectile">
  <path  fill="url(#Lg2)" transform="translate(0 -4)" d="M.8 1 .7 5.9l9.2.3s3.7-.4 3.7-2.4c.1-1.9-3.3-2.6-3.3-2.6z"  stroke="#A08B00" stroke-width="1"  >
  <animateMotion id="anPop"
    begin="svg1.click;rollback.end+2s"
    dur="3s"  rotate="auto" 
    keyPoints="0;0.4;0.6;1"
    keyTimes="0;0.3;0.6;1"
    calcMode="linear"
    repeatCount="1"
    restart="whenNotActive">
      <mpath xlink:href="#trace"/>
    </animateMotion>  
 </path> 
 </g>
 <animateTransform id="rollback" xlink:href="#cannon" attributeName="transform" type="translate" begin="anPop.begin+0.25s" dur="0.8s" values="0,0;-20,0;0,0" repeatCount="1" />  
 </svg>

0
Sean On

The only way I've been able to make the speed smoothly behave according to projectile motion (decelerating upwards, accelerating downwards) is to break the animation into its x and y components, and then use custom easing functions (splines).

The horizontal animation is easy, since the object's horizontal velocity is constant. We can animate it between beginning and end at a constant speed:

<animate id="horizontal"
         attributeName="cx"
         dur="5s"
         values="250;462" />

The vertical component has to be broken into two animations—one for its upward path and one for its downward path. You can make them sequential by adding an id to the first and referencing it in the begin attribute of the second.

Note the different durations and values.

<animate id="vertical-up"
         attributeName="cy"
         dur="1.2s"
         values="250;225" />

<animate id="vertical-down"
         attributeName="cy"
         dur="3.8s"
         values="225;487"
         begin="vertical-up.end" />

However, we still need to make it slow down as it ascends, and speed up as it descends. This can be done by—

  • Setting the calcMode of the animation to spline
  • Adding keyTimes of 0 and 1
  • And then adding keySplines values that map the motion of the projectile to the parabolic path.
<animate id="vertical-up"
         attributeName="cy"
         dur="1.2s"
         values="250;225"
         calcMode="spline"
         keyTimes="0;1"
         keySplines="0.61 1 0.88 1" />

<animate id="vertical-down"
         attributeName="cy"
         dur="3.8s"
         values="225;487"
         begin="vertical-up.end"
         calcMode="spline"
         keyTimes="0;1"
         keySplines="0.11 0 0.5 0" />

This last step took some trial and error. I found easings.net to be a good resource in understanding what the values do.

Altogether, this results in an object that follows the parabolic curve while its horizontal velocity remains constant and its vertical velocity accelerates downward at a constant rate—just like the motion of a projectile.

Click the SVG to replay the animation.

<svg id="svg" viewBox="0 0 500 500" width="300">
  <path fill="none" stroke="black" d="M250,250 Q356.06601717798213,143.93398282201787 462.13203435596427,487.86796564403573"></path>
  <circle r="5" fill="red" cx="250" cy="250">
    <animate attributeName="cx" dur="5s" values="250;462" begin="svg.load; svg.click"/>
    <animate id="y1" attributeName="cy" dur="1.2s" values="250;225" begin="svg.load; svg.click" calcMode="spline" keyTimes="0;1" keySplines="0.61 1 0.88 1"/>
    <animate attributeName="cy" dur="3.8s" values="225;487" begin="y1.end" calcMode="spline" keyTimes="0;1" keySplines="0.11 0 0.5 0"/>
  </circle>
</svg>