Skip to content

JavaScript SVGLine connecting moving elements animation

I have two SVG rectangles; two of their corners are connected by a SVGLine, and I am trying to animate the whole.

Now the rectangles are moving to a new position using the Element.animate() function (the new positions have to be computed at runtime so I think it is only possible with the animate() function in JS?).
Until that point everything works fine, but when I try to animate the line so that it is still connecting the corners during the animation, it won´t move.
Is there any way to animate the lines movement to the new position? (I can´t just set the attribute to the new position).

If I have to use a <path> or <polyline> or something for that a quick explanation on how I should do that would be great, as path.animate([{points:...},{points:...}],{...}) also didn’t move the path as i wanted.

This is a quick code example which I think should work, but the line won’t move.

let svg = document.querySelector("#theSVG");
const SVGNS = "http://www.w3.org/2000/svg";

function drawing() {
  let rect = document.createElementNS(SVGNS, "rect");
  rect.setAttribute("x", 100);
  rect.setAttribute("y", 100);
  rect.setAttribute("width", 100);
  rect.setAttribute("height", 100);
  rect.setAttribute("stroke", "black");
  svg.appendChild(rect);

  let rect2 = document.createElementNS(SVGNS, "rect");
  rect2.setAttribute("x", 10);
  rect2.setAttribute("y", 10);
  rect2.setAttribute("width", 50);
  rect2.setAttribute("height", 25);
  rect2.setAttribute("stroke", "black");
  svg.appendChild(rect2);

  let line = document.createElementNS(SVGNS, "line");
  line.setAttribute("x1", rect.x.baseVal.value);
  line.setAttribute("x2", rect2.x.baseVal.value);
  line.setAttribute("y1", rect.y.baseVal.value);
  line.setAttribute("y2", rect2.y.baseVal.value);
  line.setAttribute("stroke", "darkgray");
  svg.appendChild(line);

  rect.animate([{
    x: rect.x.baseVal.value
  }, {
    x: '200px'
  }], {
    duration: 5000,
    iterations: 1
  });
  rect2.animate([{
    y: rect2.y.baseVal.value
  }, {
    y: '300px'
  }], {
    duration: 5000,
    iterations: 1
  });

  line.animate([{
    x1: line.x1.baseVal.value,
    y2: line.y2.baseVal.value
  }, {
    x1: '200px',
    y2: '300px'
  }], {
    duration: 5000,
    iterations: 1
  });
}
drawing();
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 700 360" id="theSVG">
</svg>

Answer

You can only animate() CSS properties. From SVG, only presentational attributes are mapped to CSS. <line>‘s x1,x2,y1 and y2 are (weirdly) absent of this list…

You should be able to replace this element with a <path> though, animating its d attribute.

let svg = document.querySelector("#theSVG");
const SVGNS = "http://www.w3.org/2000/svg";

function drawing() {
  let rect = document.createElementNS(SVGNS, "rect");
  rect.setAttribute("x", 100);
  rect.setAttribute("y", 100);
  rect.setAttribute("width", 100);
  rect.setAttribute("height", 100);
  rect.setAttribute("stroke", "black");
  svg.appendChild(rect);

  let rect2 = document.createElementNS(SVGNS, "rect");
  rect2.setAttribute("x", 10);
  rect2.setAttribute("y", 10);
  rect2.setAttribute("width", 50);
  rect2.setAttribute("height", 25);
  rect2.setAttribute("stroke", "black");
  svg.appendChild(rect2);

  let line = document.createElementNS(SVGNS, "path");
  line.setAttribute("d", `
    M${ rect.x.baseVal.value } ${ rect.y.baseVal.value }
    L${ rect2.x.baseVal.value } ${ rect2.y.baseVal.value }
  `);
  line.setAttribute("stroke", "darkgray");
  svg.appendChild(line);

  rect.animate([{
    x: '200px'
  }], {
    duration: 5000,
    iterations: 1
  });
  rect2.animate([{
    y: '300px'
  }], {
    duration: 5000,
    iterations: 1
  });

  line.animate([{
    d: `path("M200 ${ rect.y.baseVal.value }L${ rect2.x.baseVal.value } 300")`
  }], {
    duration: 5000,
    iterations: 1
  });
}
drawing();
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 700 360" id="theSVG">
</svg>

But only Chromium based browsers do support CSS path() for the d property…

So you may have to switch to SMIL animations instead:

let svg = document.querySelector("#theSVG");
const SVGNS = "http://www.w3.org/2000/svg";

function drawing() {
  const rect = document.createElementNS(SVGNS, "rect");
  rect.setAttribute("x", 100);
  rect.setAttribute("y", 100);
  rect.setAttribute("width", 100);
  rect.setAttribute("height", 100);
  rect.setAttribute("stroke", "black");
  svg.appendChild(rect);
  const rectAnimateX = document.createElementNS(SVGNS, "animate");
  rectAnimateX.setAttribute("attributeName", "x");
  rectAnimateX.setAttribute("values", "100;200");
  rectAnimateX.setAttribute("dur", "5s");
  rect.append(rectAnimateX);
  
  let rect2 = document.createElementNS(SVGNS, "rect");
  rect2.setAttribute("x", 10);
  rect2.setAttribute("y", 10);
  rect2.setAttribute("width", 50);
  rect2.setAttribute("height", 25);
  rect2.setAttribute("stroke", "black");
  svg.appendChild(rect2);
  const rectAnimateY = document.createElementNS(SVGNS, "animate");
  rectAnimateY.setAttribute("attributeName", "y");
  rectAnimateY.setAttribute("values", "10;300");
  rectAnimateY.setAttribute("dur", "5s");
  rect2.append(rectAnimateY);

  const line = document.createElementNS(SVGNS, "line");
  line.setAttribute("x1", rect.x.baseVal.value);
  line.setAttribute("y1", rect.y.baseVal.value);
  line.setAttribute("x2", rect2.x.baseVal.value);
  line.setAttribute("y2", rect2.y.baseVal.value);
  line.setAttribute("stroke", "darkgray");
  svg.appendChild(line);
  const lineAnimateX1 = document.createElementNS(SVGNS, "animate");
  lineAnimateX1.setAttribute("attributeName", "x1");
  lineAnimateX1.setAttribute("values", `${rect.x.baseVal.value};200`);
  lineAnimateX1.setAttribute("dur", "5s");
  line.append(lineAnimateX1);
  const lineAnimateY2 = document.createElementNS(SVGNS, "animate");
  lineAnimateY2.setAttribute("attributeName", "y2");
  lineAnimateY2.setAttribute("values", `${rect2.y.baseVal.value};300`);
  lineAnimateY2.setAttribute("dur", "5s");
  line.append(lineAnimateY2);
}
drawing();
<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 700 360" id="theSVG">
</svg>