SVG animate a bezier curve to grow by following an arrowhead

Viewed 279

I'm trying to create an arrow with its arrowhead moving from a starting point and ending at its target. Moving the arrowhead was achieved successfully with the tip from @Robert Longson. I want the end of the shaft also to follow the arrowhead and grow to its full length. The code is given below and notice that the shaft isn't growing with the arrowhead and also ends up in partial length. Is there a way to correct this. Any help will be greatly appreciated.

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="5 0 100 45">
    <style>
        .wire {
            fill: none;
            stroke: red;
            stroke-width: 1px;

            /* Stroke-dasharray property to animate */
            stroke-dasharray: 100%;
            stroke-dashoffset: 100%;
            animation: move linear 5s;
        }

        @keyframes move {
            100% {
                stroke-dashoffset: 0;
            }
        }
    </style>
    <path d="M10,10 C15,50 95,50 100,10" stroke="blue" stroke-width="2" id="wire" class="wire">
        <animate>
            <mpath xlink:href="#wire" />
        </animate>
    </path>
    <!-- acceptable movement along the path but incorrect orientation -->
    <polygon points="-5,-5 5,0 -5,5 -3,0" fill="red">
        <animateMotion dur="5s" repeatCount="1" rotate="auto" fill="freeze">
            <mpath xlink:href="#wire" />
        </animateMotion>
    </polygon> 
</svg>

2 Answers

Like this I guess. Note that you could have done this in SMIL too.

I've set the animation to be forwards so it remains at the end, otherwise the curve disappears at the end of the animation.

The console.log line shows you where I got the number from.

console.log(document.getElementsByTagName("path")[0].getTotalLength())
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="5 0 100 45">
    <style>
        .wire {
            fill: none;
            stroke: red;
            stroke-width: 1px;

            /* Stroke-dasharray property to animate */
            stroke-dasharray: 118.27912902832031;
            stroke-dashoffset: 118.27912902832031;
            animation: move linear 5s forwards;
        }

        @keyframes move {
            100% {
                stroke-dashoffset: 0;
            }
        }
    </style>
    <path d="M10,10 C15,50 95,50 100,10" stroke="blue" stroke-width="2" id="wire" class="wire">
        <animate>
            <mpath xlink:href="#wire" />
        </animate>
    </path>
    <!-- acceptable movement along the path but incorrect orientation -->
    <polygon points="-5,-5 5,0 -5,5 -3,0" fill="red">
        <animateMotion dur="5s" repeatCount="1" rotate="auto" fill="freeze">
            <mpath xlink:href="#wire" />
        </animateMotion>
    </polygon> 
</svg>

To move the cursor and line at the same time:

  1. It is necessary to synchronize both animations in start time begin="svg1.click" and duration dur="5s"

The maximum curve length is 118.3px so

stroke-dashoffset:118.3px;
stroke-dasharray:118.3px;

#wire {
stroke-dashoffset:118.3px;
stroke-dasharray:118.3px;
stroke:red;
stroke-width:2px;
fill:none;
}
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="5 0 100 45">
    <style>
       
    </style>
    <path id="wire" d="M10,10 C15,50 95,50 100,10" >  
       <!-- Curved path growth animation -->
        <animate
           attributeName="stroke-dashoffset"
           begin="svg1.click"
           dur="5s"
           values="118.3;0"
           calcMode="linear"
           fill="freeze"
           restart="whenNotActive" /> 
        </animate>
    </path>
    <!-- Cursor -->
    <polygon points="-5,-5 5,0 -5,5 -3,0" fill="red">
          <!-- Animating cursor movement along a curved path -->
        <animateMotion begin="svg1.click" dur="5s" repeatCount="1" calcMode="linear" rotate="auto" fill="freeze">
            <mpath xlink:href="#wire" />
        </animateMotion>
    </polygon>  
    <text x="40" y="20" font-size="6px" fill="dodgerblue">Click me</text>
</svg>  
<script>
var path = document.querySelector('#wire');
        var len = (path.getTotalLength() );
        console.log("Path length - " + len);
</script>       

UPDATE

as a bonus: Moving forward - backward

  • To control the direction of movement, add two buttons and an onclick event
<div>
    <button onclick="forward.beginElement()">forward</button>
    <button onclick="back.beginElement()">back</button>
</div>  
  • The direction of the cursor is controlled by a couple of arguments:
keyPoints="0;1"
keyTimes="0;1"
  • Animation of line growing or falling depends on the value of stroke-dashoffset

values="118.3;0" - line growth values="0;118.3" - decrease line`

#wire {
stroke-dashoffset:118.3px;
stroke-dasharray:118.3px;
stroke:red;
stroke-width:2px;
fill:none;
}
svg {
width:50%;
height:50%;
}
<div>
    <button onclick="forward.beginElement()">forward</button>
    <button onclick="back.beginElement()">back</button>
</div>  
<svg id="svg1" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="5 0 100 45">
    <style>
       
    </style>
    <path id="wire" d="M10,10 C15,50 95,50 100,10" >  
       <!-- Animation of filling, growing a line (`forward`)-->
        <animate id="strokeForward"
           attributeName="stroke-dashoffset"
           begin="forward.begin"
           dur="5s"
           values="118.3;0"
           calcMode="linear"
           
           fill="freeze"
           restart="whenNotActive" /> 
          <!-- String decrease animation (`back`) -->
       <animate id="strokeBack"
           attributeName="stroke-dashoffset"
           begin="back.begin"
           dur="5s"
           values="0;118.3"
           calcMode="linear"
           fill="freeze"
           restart="whenNotActive" /> 
       
    </path>
    <!-- Cursor -->
    <polygon points="-5,-5 5,0 -5,5 -3,0" fill="red">
          <!-- Animating cursor movement along a curved path (`forward`) -->
    <animateMotion
          id="forward"
          begin="indefinite"
          dur="5s"
          repeatCount="1"
          calcMode="linear"
          rotate="auto"
          fill="freeze"
          keyPoints="0;1"
          keyTimes="0;1"
          restart="whenNotActive">
            <mpath xlink:href="#wire" />
        </animateMotion>    
        <!-- Animating cursor movement along a curved path (`back`) -->
         <animateMotion
          id="back"
          begin="indefinite"
          dur="5s"
          repeatCount="1"
          calcMode="linear"
          rotate="auto"
          fill="freeze"
          keyPoints="1;0"
          keyTimes="0;1"
          restart="whenNotActive">
            <mpath xlink:href="#wire" />
        </animateMotion>  
    </polygon>  
    </svg> 

Related