wave animation on each path of the svg

230 Views Asked by At

In my react project, I have an SVG that contains horizontal lines (path), and when I hover the mouse on each path I want to start wave animation that starts from the hovered point and continuously ends until the end of the path line.

as default, I use a vibration CSS animation. and I want to replace vibration with wave

I don't have any idea about that. I'm not into the SVG.

I want something like this GIF.

enter image description here

my react component

"use client";
import { useCallback } from "react";
import "./styles.css";
const SongPlayer = () => {
  const soundFontUrl = "/sounds/guitar/";
  const playNote = useCallback((note: string) => {
    const audio = new Audio(soundFontUrl + note.trim() + ".mp3");
    audio.play();
  }, []);

  const handleMouseOver = (note: string, id: string) => {
    playNote(note);
    const el = document.getElementById(id);
    el?.classList.add("vibrating");

    setTimeout(() => {
      el?.classList.remove("vibrating");
    }, 1000);
  };

  return (
    <div className="w-full mt-[44px]">
      <div className="w-full flex justify-center items-center">
        <svg
          id="guitar"
          className="guitar"
          width="1700"
          height="324"
          viewBox="0 0 1441 324"
          fill="none"
          xmlns="http://www.w3.org/2000/svg"
        >
    
          <path
            id="g-one"
            onMouseOver={() => handleMouseOver("D3", "g-one")}
            d="M1075.88 242.167C1059.69 248.627 1044.41 255.881 1021.79 251.862C1020.83 251.69 1019.91 251.509 1019.03 251.319M1019.03 251.319C987.538 244.521 1010.27 226.472 1021.79 232.056C1028.69 235.4 1025.2 244.393 1019.03 251.319ZM1019.03 251.319C1014.6 256.283 1008.47 258.373 993.68 258.373C954.721 258.373 905.598 232.907 799 235.49L0 235.49"
            stroke="#A39D92"
            strokeWidth="1.5"
          />
          <path
            id="g-two"
            onMouseOver={() => handleMouseOver("D5", "g-two")}
            d="M0 275H1089.5"
            stroke="#A39D92"
            strokeWidth="1.5"
          />
          <path
            id="g-three"
            onMouseOver={() => handleMouseOver("C4", "g-three")}
            d="M0 164.5H1026"
            stroke="#A39D92"
            strokeWidth="1.5"
          />
          <path
            id="g-four"
            onMouseOver={() => handleMouseOver("C2", "g-four")}
            d="M0 125H1057.5"
            stroke="#A39D92"
            strokeWidth="1.5"
          />
          <path
            id="g-five"
            onMouseOver={() => handleMouseOver("F4", "g-five")}
            d="M0 54H1151"
            stroke="#A39D92"
            strokeWidth="1.5"
          />
          <path
            id="g-six"
            onMouseOver={() => handleMouseOver("F2", "g-six")}
            d="M0 14.5H1164.5"
            stroke="#A39D92"
            strokeWidth="1.5"
          />
        </svg>
      </div>
    </div>
  );
};

export default SongPlayer;
2

There are 2 best solutions below

1
Michael Rovinsky On BEST ANSWER

Here is an attempt (with D3 library):

const svg = d3.select('svg');

const totalWidth = 300;
const waveLength = 30;
const maxMagnitude = 40;
const delta = 5;
const maxIndex = 22;

const line = d3.line().curve(d3.curveMonotoneX);

const nextStep = (handle, points, y, index) => {
  const pArray = points.map(p => [p.x, p.y]);
    
  points.forEach(p => {
        if (p.m > 0) {
        p.m -= 2;
        if (p.y > y) 
            p.y = y - p.m;
        else
            p.y = y + p.m;
      }
  });
  if (index < maxIndex) {
    const path = line(pArray);
    d3.select(handle)
      .transition()
      .duration(250)
      .attr('d', path);
    setTimeout(() => nextStep(handle, points, y, index + 1), 250);
  }
  else {
    d3.select(handle).attr('d', `M 0,${y} H 300`);
  }
 }


const onHover = (handle, e, lineIndex) => {
  let x = e.offsetX;
  let y = lineIndex * 30;
  let m = maxMagnitude;
  const start = - Math.ceil(x / waveLength);
  const end = Math.ceil((totalWidth - x) / waveLength);
  
  const points = [];
  
  for (let i = 0; i >= start; i--) {
    points.unshift({x, y, m: 0});
    x -= waveLength / 4;
    points.unshift({x, y: y - m, m});
    m = Math.max(0, m - delta);
    x -= waveLength / 4;
    points.unshift({x, y: y, m: 0});
    x -= waveLength / 4;
    points.unshift({x, y: y + m, m});
    m = Math.max(0, m - delta);
    x -= waveLength / 4;
  }

  m = maxMagnitude;
  x = e.offsetX;
  for (let i = 0; i <= end; i++) {
    if (i !== 0)
        points.push({x, y, m: 0});
    x += waveLength / 4;
    points.push({x, y: y + m, m});
    m = Math.max(0, m - delta);
    x += waveLength / 4;
    points.push({x, y: y, m: 0});
    x += waveLength / 4;
    points.push({x, y: y - m, m});
    m = Math.max(0, m - delta);
    x += waveLength / 4;
  }
  
  nextStep(handle, points, y, 0);
}

for (let i = 0; i <= 10; i++) {
    svg.append('path')
    .attr('d', `M 0,${i * 30} H 300`)
    .attr('fill', 'none')
    .attr('stroke', 'blue')
    .on('mouseover', function(e) {
        onHover(this, e, i);
    });
}
svg {
  border: 1px solid blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
<svg width='300' height='300'>
</svg>

0
chrwahl On

Don't know if you find this useful. I update the d attribute on the path element using the setInterval() function. I use the Quadratic Bézier Curve. It works as long as the amplitude is the same. If you need variable amplitudes on the same path this should be a Cubic Bézier Curve instead, but it's more complicated to calculate.

In the ideal setup you be to switch out setInterval() with requestAnimationFrame(), but that is another day.

const path = document.querySelector('path');
let dist = 0;
let freq = 20;
let amp = 0;
let ampdelta = .4;
let length = 140;
let arr = [...new Array(length / freq).keys()];
let interval = 0;

path.addEventListener('mouseover', () => {
  if (interval == 0) {
    amp = 0;
    ampdelta = .1;
    interval = setInterval(animate, 10);
  }
});

function animate() {
  path.setAttribute('d', arr.map(i => {
    let prestr = (i == 0) ? `m ${dist} 0 ` : '';
    let localamp = (i % 2) ? amp * -1 : amp;
    return prestr + `q ${freq/2} ${localamp} ${freq} 0`;
  }).join(' '));

  dist = (dist >= -1) ? freq * -2 : dist + 1;
  amp = amp + ampdelta;
  if (amp > 10) ampdelta = ampdelta * -1;
  if (amp < 0) {
    clearInterval(interval);
    interval = 0;
  }
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 40">
  <path transform="translate(0 20)" d="m 0 0 h 100" stroke="black" fill="none" />
</svg>