Javascript to animate a new SVG polygon each time you click

Sorry for newbie question. I have set myself a really simple task but I am failing to get the Javascript right. I have a polygon with three different states: Star, Pentagram, Square. The star shape is set as the default. I would like to introduce a click function so that the first time you click, the star morphs into a pentagram. When you click on the resulting pentagram it morphs into a square. Click on that square and you return to the star again. And so on and so on…

I think my below HTML code looks decent enough. But I am messing things up in the .js file. (I won’t even post it here, as I think it may create more confusion than it resolves). Most related questions I have found here on StackOverflow discusses how to initiate css transitions, not changes happening directly in the HTML file.

Here’s the HTML setup of my SVG file(s).

<svg viewBox="0 0 700 700" id="clickMe">
    <polygon class="Star" points="49.72,278.76 277.96,278.76 350.21,50.01 422.12,279.39 650.37,279.39 464.74,422.99 536.15,650.01 350.19,507.39 164.7,649.93 234.61,422.17 " style="fill:none;stroke:black;stroke-width:10;stroke-linecap:round;">
           
        <animate class="Star2Pentagram" begin="indefinite" attributeName="points" dur="1000ms" 
            from="49.72,278.76 277.96,278.76 350.21,50.01 422.12,279.39 650.37,279.39 464.74,422.99 536.15,650.01 350.19,507.39 164.7,649.93 234.61,422.17 "
            to="49.72,278.76 170.8,187.15 350.21,50.01 492.4,158.03 650.37,279.39 596.18,453 536.15,650.01 350.04,650.01 164.7,649.93 108.9,470.12 " 
            fill="freeze"></animate>

        <animate class="Pentagram2Square" begin="indefinite" attributeName="points" dur="1000ms" 
            from="49.72,278.76 170.8,187.15 350.21,50.01 492.4,158.03 650.37,279.39 596.18,453 536.15,650.01 350.04,650.01 164.7,649.93 108.9,470.12 "
            to="49.72,278.76 50.42,50.01 350.21,50.01 650.21,50.01 650.37,279.39 650.21,650.01 536.15,650.01 350.04,650.01 164.7,649.93 50.42,650.01 " 
            fill="freeze"></animate>

        <animate class="Square2Star" begin="indefinite" attributeName="points" dur="1000ms" 
            from="49.72,278.76 50.42,50.01 350.21,50.01 650.21,50.01 650.37,279.39 650.21,650.01 536.15,650.01 350.04,650.01 164.7,649.93 50.42,650.01 "
            to="49.72,278.76 277.96,278.76 350.21,50.01 422.12,279.39 650.37,279.39 464.74,422.99 536.15,650.01 350.19,507.39 164.7,649.93 234.61,422.17 " 
            fill="freeze"></animate>

    </polygon>
</svg>

Answer

Instead of having all three <animate> elements already in the SVG, try to create and add them dynamically. By doing this you only have the relevant animation in the SVG.

The script below uses an array of shapes to get the current and the next shape to create an <animation> element with. That element is then added to the <polygon> on a click. With each click it cycles through the shapes array.

const svg = document.getElementById('clickMe');
const polygon = svg.querySelector('polygon');

// Flag to keep track if an animation is running.
let isAnimating = false;

// List of shapes with the current shape index.
let currentShapeIndex = 0;
const shapes = [
  // star
  '49.72,278.76 277.96,278.76 350.21,50.01 422.12,279.39 650.37,279.39 464.74,422.99 536.15,650.01 350.19,507.39 164.7,649.93 234.61,422.17',
  
  // pentagon
  '49.72,278.76 170.8,187.15 350.21,50.01 492.4,158.03 650.37,279.39 596.18,453 536.15,650.01 350.04,650.01 164.7,649.93 108.9,470.12', 
  
  // square
  '49.72,278.76 50.42,50.01 350.21,50.01 650.21,50.01 650.37,279.39 650.21,650.01 536.15,650.01 350.04,650.01 164.7,649.93 50.42,650.01'
];

/**
 * Gets the current and the next shape.
 * With this you can get a from and to attribute value for creating the <animation> element.
 * @returns {Array<string, string>}
 */
const getShapes = () => {
  const currentShape = shapes[currentShapeIndex];

  const nextShapeIndex = currentShapeIndex + 1;
  if (nextShapeIndex < shapes.length) {
    currentShapeIndex = nextShapeIndex;
  } else {
    currentShapeIndex = 0;
  }

  const nextShape = shapes[currentShapeIndex];
  return [currentShape, nextShape];
};

// Default attributes for every <animate> element.
const defaultAttributes = {
  attributeName: 'points',
  dur: '1000ms',
  fill: 'freeze',
  begin: '0',
}

/**
 * Creates an <animation> element with the attributes set.
 * @param {string} from Points string to animate from.
 * @param {string} to Points string to animate to.
 * @param {object} [options=defaultAttributes] Optional options object to set attributes.
 * @returns {SVGAnimateElement}
 */
const createAnimateElement = (from, to, options = defaultAttributes) => {
  const animateElement = document.createElementNS('http://www.w3.org/2000/svg', 'animate');

  const attributes = {
    from,
    to,
    ...options
  };

  for (const [name, value] of Object.entries(attributes)) {
    animateElement.setAttribute(name, value);
  }

  return animateElement;
}

svg.addEventListener('click', () => {
  // Don't do anything if an animation is running.
  if (isAnimating) {
    return;
  }

  // Animation is running.
  isAnimating = true;

  // If there is a previous <animation> element, remove it.
  const previousAnimateElement = svg.querySelector('animate');
  if (previousAnimateElement) {
    previousAnimateElement.remove();
  } 

  // Create the <animation> element with the current and next shape.
  const [currentShape, nextShape] = getShapes();
  const animateElement = createAnimateElement(currentShape, nextShape);

  animateElement.addEventListener('endEvent', () => {
    // Update the polygon points.
    polygon.setAttribute('points', nextShape);

    // Set the flag back to false when the animation has finished.
    isAnimating = false;
  });

  // Add the animation to the polygon.
  polygon.append(animateElement);

  // Begin the animation from the start.
  animateElement.beginElement();
});
<svg viewBox="0 0 700 700" id="clickMe">
  <polygon 
  class="Star" 
  points="49.72,278.76 277.96,278.76 350.21,50.01 422.12,279.39 650.37,279.39 464.74,422.99 536.15,650.01 350.19,507.39 164.7,649.93 234.61,422.17" 
  style="fill: none; stroke: black; stroke-width: 10; stroke-linecap: round;"
  ></polygon>
</svg>