Search code examples
reactjssvgsvg-animate

wave animation on each path of the svg


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;

Solution

  • 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>