JSFiddle - React, Tailwind, and code Playground

HTML

<svg id="wave" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     viewBox="0 0 456 100" style="enable-background:new 0 0 456 100;" xml:space="preserve">
<style type="text/css">
    .st69{fill:none;stroke:#000000;stroke-width:12;stroke-miterlimit:10;}
</style>
    <circle cx="50" cy="50" r="4"  fill="red" />
    <circle cx="100" cy="50" r="4"  fill="red" />
    <circle cx="150" cy="50" r="4"  fill="red" />
    <circle cx="200" cy="50" r="4" fill="red" />
    <circle cx="250" cy="50" r="4"  fill="red" />
    <circle cx="300" cy="50" r="4"  fill="red" />
    <circle cx="350" cy="50" r="4"  fill="red" />
    <circle cx="400" cy="50" r="4" fill="red" />
</svg>v

JavaScript

const NS = 'http://www.w3.org/2000/svg';
const svg = document.querySelector('svg');
const circles = [...document.querySelectorAll('svg circle')];
const lines = [];

for (let i = 0; i < circles.length - 1; i++) {
  const c0 = circles[i];
  const c1 = circles[i + 1];
  const line = document.createElementNS(NS, 'line');
  line.setAttribute('stroke-width', 1);
  line.setAttribute('stroke', 'black');
  line.setAttribute('x1', c0.getAttribute('cx'));
  line.setAttribute('y1', c0.getAttribute('cy'));
  line.setAttribute('x2', c1.getAttribute('cx'));
  line.setAttribute('y2', c1.getAttribute('cy'));
  lines.push(line);
  svg.appendChild(line);
}

function anim() {
	const now = performance.now();
  circles.forEach((c, i) => {
    c.setAttribute('cy', 50 + Math.sin(now / 1000 + i) * 25);
  });
  lines.forEach((c, i) => {
    c.setAttribute('y1', 50 + Math.sin(now / 1000 + i) * 25);
    c.setAttribute('y2', 50 + Math.sin(now / 1000 + i + 1) * 25);
  });
}
setInterval(anim, 20);