0

I'm having massive headaches trying to fin the sweet-spot to achieve "8" shape animation using the combination of just <animate attributeName="cx"> and <attributeName="cy">.
I'll like to do it using that since it seems, by my metrics, that it's the most performant in terms of FPS, CPU and GPU usage.

Quick demo of the "ideal" motion path: https://codepen.io/ivancis/pen/eYmZowz

Alexandr_TT
  • 13,635
  • 3
  • 27
  • 54
ivancis
  • 21
  • 3
  • 1
    Welcome to Stack Overflow! Questions seeking code help must include the shortest code necessary to reproduce it **in the question itself** preferably in a **Stack Snippet**. Although you have provided a link, if it was to become invalid, your question would be of no value to other future SO users with the same problem. See [**Something in my website OR off-site example doesn't work can I just paste a link**](http://meta.stackoverflow.com/questions/254428/something-in-my-web-site-or-project-doesnt-work-can-i-just-paste-a-link-to-it). – Paulie_D Jan 16 '20 at 15:31

3 Answers3

2

You didn’t say what kind of animation you really want.

Therefore, I will offer examples of different types of animation and you can choose any them and somehow modify for yourself.

Infinity Symbol Moving Animation

<svg xmlns="http://www.w3.org/2000/svg"  width="400" height="100" viewBox="0 0 100 100">
  
  <path fill="none" stroke="dodgerblue" d="M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z" stroke="#d3d3d3" stroke-width="10">  
        <animateTransform
          attributeName="transform"
          type="translate"
          values="0; 150; 0"
          begin="0s"
          dur="4s"
          repeatCount="indefinite" /> 
 </path> 
</svg> 

Rotation

Mouse over symbol

.infinity1{
transform-box: fill-box;
 transform-origin: center center;
 transition: rotate 2s linear ;
}
.infinity1:hover {
animation: spin 2s linear infinite;
}

@keyframes spin {
100% {transform :rotate(360deg);}

}
<svg xmlns="http://www.w3.org/2000/svg"  width="100" height="100" viewBox="0 0 100 100">
  
  <path class="infinity1" fill="none" stroke="dodgerblue" d="M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z" stroke="#d3d3d3" stroke-width="10"  />  
        
 
</svg>

Rotation around the axis Y

.infinity1{
transform-box: fill-box;
 transform-origin: center center;
 transition: rotate 2s linear ;
 fill:transparent;
}
.infinity1:hover {
animation: spin 2s linear infinite;
}

@keyframes spin {
100% {transform :rotateY(360deg);}

}
<svg xmlns="http://www.w3.org/2000/svg"  width="100" height="100" viewBox="0 0 100 100" >
  
  <path class="infinity1"  stroke="dodgerblue" d="M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z" stroke="#d3d3d3" stroke-width="10"  />  
        
 
</svg> 

Animation of infinity symbol filling by changing attribute stroke-dasharray

Click on the colored letters in circles

.container {
width:40%;
height="40%";  
  
  background:black;
}
<div class="container">
<svg xmlns="http://www.w3.org/2000/svg"  viewBox="0 20 100 100">
  
  <path fill="none" d="M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z" stroke="#d3d3d3" stroke-width="10"  />  
       <!-- The midpoint of the beginning of the animation in the center of the figure. stroke-dashoffset="31.1" -->
 <path id="center" fill="none" d="M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z" stroke="crimson" stroke-width="10" stroke-dashoffset="31.1" stroke-dasharray="0 128.5" >  
      <animate  attributeName="stroke-dasharray" values="0 128.5 0 128.5;0 0 257 0" begin="btn_C.click" dur="4s" restart="whenNotActive" /> 
 </path> 
   <!-- Middle point on the left stroke-dashoffset="-159.5" -->
     <path id="Left" fill="none" d="M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z" stroke="yellowgreen" stroke-width="10" stroke-dashoffset="-159.5" stroke-dasharray="0 128.5" >  
      <animate attributeName="stroke-dasharray" values="0 128.5 0 128.5;0 0 257 0" begin="btn_L.click" dur="4s"  restart="whenNotActive" /> 
 </path>  
 
    <!-- Midpoint left top stroke-dashoffset="128.5" -->
     <path id="Top" fill="none" d="M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z" stroke="gold" stroke-width="10" stroke-dashoffset="128.5" stroke-dasharray="0 128.5" >  
      <animate attributeName="stroke-dasharray" values="0 128.5 0 128.5;0 0 257 0" begin="btn_T.click" dur="4s"  restart="whenNotActive" /> 
 </path> 
     <!-- Midpoint lower right  stroke-dashoffset="192.7" -->
  <path id="Bottom" fill="none" d="M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z" stroke="dodgerblue" stroke-width="10" stroke-dashoffset="192.7" stroke-dasharray="0 128.5" >  
      <animate attributeName="stroke-dasharray" values="0 128.5 0 128.5;0 0 257 0" begin="btn_B.click" dur="4s"  restart="whenNotActive" /> 
 </path>   
 
        <!-- Middle point on the right   stroke-dashoffset="223.9" -->
  <path id="Bottom" fill="none" d="M24.3 30C11.4 30 5 43.3 5 50s6.4 20 19.3 20c19.3 0 32.1-40 51.4-40C88.6 30 95 43.3 95 50s-6.4 20-19.3 20C56.4 70 43.6 30 24.3 30z" stroke="purple" stroke-width="10" stroke-dashoffset="223.9" stroke-dasharray="0 128.5" >  
      <animate attributeName="stroke-dasharray" values="0 128.5 0 128.5;0 0 257 0" begin="btn_R.click" dur="4s"  restart="whenNotActive" /> 
 </path> 
 
 
 
 <g id="btn_L" transform="translate(-17 0)" >
      <rect x="20" y="84" width="15" height="15" rx="7.5" fill="none" stroke="#B2B2B2"/>
      <text x="25" y="95" font-size="10" fill="green" >L</text>
    </g>  
 <g id="btn_C" transform="translate(3 0)">
      <rect x="20" y="84" width="15" height="15" rx="7.5" fill="none" stroke="#B2B2B2"/>
      <text x="24" y="95" font-size="10" fill="crimson" >C</text>
    </g> 
    
     <g id="btn_T" transform="translate(23 0)">
      <rect x="20" y="84" width="15" height="15" rx="7.5" fill="none" stroke="#B2B2B2"/>
      <text x="24" y="95" font-size="10" fill="orange" >T</text>
        </g>  
  <g id="btn_B" transform="translate(43 0)">
 <rect x="20" y="84" width="15" height="15" rx="7.5" fill="none" stroke="#B2B2B2"/>
  <text x="25" y="95" font-size="10" fill="dodgerblue" >B</text>
  </g>   
      <g id="btn_R" transform="translate(63 0)">
     <rect x="20" y="84" width="15" height="15" rx="7.5" fill="none" stroke="#B2B2B2"/>
       <text x="25" y="95" font-size="10" fill="purple" >R</text>
    </g> 
</svg>
</div>

Live Demo

Alexandr_TT
  • 13,635
  • 3
  • 27
  • 54
  • Agh men you went above and beyond, thank you! But I must say it wasn't what I was after ... I need to animate a svg element using just a combination of (for performance) to make an "8" shape motion, looped. – ivancis Jan 20 '20 at 15:19
  • @ivancis Can I try again to do as you said in the additional answer? – Alexandr_TT Jan 20 '20 at 15:42
  • Maybe this helps: https://codepen.io/diazivanluis/pen/MWYzXPm I guess the answer is behind somewhere keySplines and bezier curve, or durations, or same/oposite values ... I've tried so much – ivancis Jan 20 '20 at 18:20
2

New solution caused by refinements in comments

I need to animate a svg element using just a combination of <animate attributeName="cx"> <animate attributeName="cy"> (for performance) to make an "8" shape motion, looped

Since the author does not want to use the animateMotion command, in this case   I see only one way to implement the animation of the movement of the circle along the infinity symbol:

It is necessary to sequentially select many points along the infinity symbol and assign their coordinates to the circle cx = "x", cy = "y"

The more points you select, the closer the trajectory moving of the circle along the infinity symbol

enter image description here

In the vector editor, I sequentially put circles on the infinity symbol and wrote down their coordinates of the center of the circle. The first circle has the center coordinates cx ="70" cy ="60"
So, it was done for all circles located along the infinity symbol. The last circle has the same coordinates as the first one, thereby realizing a closed cycle

It remains only to substitute these values in the animation formulas cx, cy

Circle motion animation cx, cy with radius r="5

 <div class="group">
  
  <svg class="ball" xmlns="http://www.w3.org/2000/svg" width="50%" height="50%" viewBox="0 0 120 120">    
  
    
    <circle fill="olive" cx="70" cy="60" r="5">
      <animate
              attributeName="cx"
              attributeType="XML"
              repeatCount="indefinite"
              begin="0s"
              dur="2s"
              values="70;65;60;55;50;45;40.5;40.5;42.5;45.1;48.7;52;55;58;60;61;61;61;61;61;61;62.9;66;69;
     73;76;79;81;80;78;74;70">
              
        </animate>
    <animate
      attributeName="cy"
      attributeType="XML"
      repeatCount="indefinite"
      begin="0"
      dur="2s"
      values="60;60;60;60;60;58.3;52.5;47.9;44.4;41.8;40.3;40;41;43;47;51;55;60;65;70;74;77;79;
      80;80;79;76;72;67;64;61;60">        
    </animate>   
 </circle>
        
    <path fill="none" stroke="black" stroke-dasharray="2" d="M70.5,60.5c5.5,0,10,4.5,10,10s-4.5,10-10,10s-10-4.5-10-10v-20c0-5.5-4.5-10-10-10s-10,4.5-10,10 s4.5,10,10,10H70.5z"/>
  </svg>
</div>
  

Radius r = 40 as in the example of the author of the question

 <div class="group">
  
  <svg class="ball" xmlns="http://www.w3.org/2000/svg" width="50%" height="50%" viewBox="0 0 120 120">    
  
    
    <circle fill="olive" cx="70" cy="60" r="40">
      <animate
              attributeName="cx"
              attributeType="XML"
              repeatCount="indefinite"
              begin="0s"
              dur="2s"
              values="70;65;60;55;50;45;40.5;40.5;42.5;45.1;48.7;52;55;58;60;61;61;61;61;61;61;62.9;66;69;
     73;76;79;81;80;78;74;70">
              
        </animate>
    <animate
      attributeName="cy"
      attributeType="XML"
      repeatCount="indefinite"
      begin="0"
      dur="2s"
      values="60;60;60;60;60;58.3;52.5;47.9;44.4;41.8;40.3;40;41;43;47;51;55;60;65;70;74;77;79;
      80;80;79;76;72;67;64;61;60">        
    </animate>   
 </circle>
        
    <path fill="none" stroke="black" stroke-dasharray="2" d="M70.5,60.5c5.5,0,10,4.5,10,10s-4.5,10-10,10s-10-4.5-10-10v-20c0-5.5-4.5-10-10-10s-10,4.5-10,10 s4.5,10,10,10H70.5z"/>
  </svg>
</div>
  
Alexandr_TT
  • 13,635
  • 3
  • 27
  • 54
  • Your answer below is awesome @Alexandr_TT especially "the colored letters in circles". Also, this answer here (with Inkscape) gave me an idea of how to solve a similar problem I was faced with without Inkscape. Would love to hear what you think and hope my answer may also help others who land on this page. – Alex L Feb 17 '20 at 16:26
  • 1
    @Alex L I think that solving the same problem with different methods is very productive and useful. When I answered this question I was very much surprised by the author’s desire not to use animateMotion in principle My answer and your answer duplicate the work of the standard SVG `animateMotion` command, which itself calculates the coordinates and moves the object along the motion path. Is there any practical benefit from our answers? This is a big question. People probably won’t want to know the details. – Alexandr_TT Feb 17 '20 at 17:08
  • @AlexL And they will continue to use the standard option for solving such problems - `animateMotion` **All in all, you did a very good job** Your method can certainly be used in such problems where a mechanical device first learns from the path traveled, remembers the coordinates and then can automatically repeat them yourself – Alexandr_TT Feb 17 '20 at 17:09
  • 1
    Thanks for the feedback! Completely agree. I had never heard that ```` was inefficient but I did respond to one use case similar to the OP, to animate only one end of a line according to a path (and keep the other end anchored) and for this, I used a similar method (https://stackoverflow.com/a/60024858/9792594). Although I agree, the use cases are limited and ```` and ```` solve most problems when used in their intended ways :) – Alex L Feb 17 '20 at 17:48
1

I was looking into a similar thing and came across this answer. @Alexandr_TT's answer got me thinking about a more flexible way to do this, without using a graphics editor (like Inkscape etc.)

I came up with the following idea:

  • Use the <AnimateMotion/> for the first loop.
  • Fire a setInterval every X milliseconds and each time it fires to capture the centre point of the circle (from circle.getBoundingClientRect() and svg.matrixTransform())
  • Push these x and y values to two arrays to capture them
  • When the AnimateMotion ends, clear the current setInterval and push the first element also to the end of each of the arrays (to close the loop)
  • Remove the <AnimateMotion/> tag from the DOM
  • Push these arrays to the values attribute of the <animate id="cx" attributeName="cx" values="" .../> and <animate id="cy" attributeName="cy" values="" .../> tags
  • begin both of these animate tags with cx.beginElement() and cy.beginElement()

You could just be happy with this performance-wise, or you could copy-paste the DOM elements with their values="..." attributes and save that as your new master file, essentially achieving what @Alexandr_TT did with the graphics editor. Of course, this method I am showing is flexible if you decide to change your path etc.

Demo: https://codepen.io/Alexander9111/pen/VwLaNEN

HTML:

<circle id="circle" class="circle" cx="0" cy="00" r="125">          
      <animateMotion
           path="M162.9,150c6.8-0.2,12.1-5.7,12.1-12.5c0-6.9-5.6-12.5-12.5-12.5c-6.8,0-12.3,5.4-12.5,12.2v25.7 c-0.2,6.8-5.7,12.2-12.5,12.2c-6.9,0-12.5-5.6-12.5-12.5c0-6.8,5.4-12.3,12.1-12.5L162.9,150z"
           dur="4s" begin="0s"
           epeatCount="1" fill="freeze"
           calcMode="linear"
           fill="freeze">
      </animateMotion>
      <animate id="cx" attributeName="cx" values="" dur="4s" repeatCount="indefinite" begin="indefinite"/>
      <animate id="cy" attributeName="cy" values="" dur="4s" repeatCount="indefinite" begin="indefinite"/>
</circle>

JS:

const svg = document.querySelector('svg');
const animateElem = document.querySelector('animateMotion');
const circle = document.querySelector('#circle');
const cx = document.querySelector('#cx');
const cy = document.querySelector('#cy');
let myInterval;
let valuesX = [];
let valuesY = [];

function startFunction() {
  const box = circle.getBoundingClientRect();
  var pt = svg.createSVGPoint();
  pt.x = (box.left + box.right) / 2;
  pt.y = (box.top + box.bottom) / 2;
  var svgP = pt.matrixTransform(svg.getScreenCTM().inverse());
  console.log(svgP.x,svgP.y)
  valuesX.push(svgP.x);
  valuesY.push(svgP.y);
}

function endFunction() {  
  animateElem.parentNode.removeChild(animateElem);
  clearInterval(myInterval)
  valuesX.push(valuesX[0]);
  valuesY.push(valuesY[0]);
  cx.setAttribute('values', valuesX.join('; '));
  cy.setAttribute('values', valuesY.join('; '));
  circle.setAttribute('cx', 0);
  circle.setAttribute('cy', 0);  
  cx.beginElement();
  cy.beginElement();
}

animateElem.addEventListener('beginEvent', () => {
  console.log('beginEvent fired');
  myInterval = setInterval(startFunction, 50);
})

animateElem.addEventListener('endEvent', () => {
  console.log('endEvent fired');
  endFunction();
})
Alex L
  • 4,168
  • 1
  • 9
  • 24