| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>SVG Path Animation</title> |
| <style> |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| min-height: 100vh; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| background: #1a1a1a; |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto; |
| color: #fff; |
| } |
| |
| .container { |
| width: 800px; |
| height: 600px; |
| position: relative; |
| } |
| |
| svg { |
| width: 100%; |
| height: 100%; |
| overflow: visible; |
| } |
| |
| path { |
| fill: none; |
| stroke: #4CAF50; |
| stroke-width: 4; |
| stroke-linecap: round; |
| stroke-linejoin: round; |
| } |
| |
| .dot { |
| fill: #ff4081; |
| r: 8; |
| filter: drop-shadow(0 0 8px rgba(255, 64, 129, 0.5)); |
| } |
| |
| .controls { |
| margin-top: 2rem; |
| display: flex; |
| gap: 1rem; |
| } |
| |
| button { |
| padding: 0.8rem 1.5rem; |
| border: none; |
| border-radius: 25px; |
| background: #4CAF50; |
| color: white; |
| font-size: 1rem; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| } |
| |
| button:hover { |
| background: #45a049; |
| transform: translateY(-2px); |
| } |
| |
| #speed-control { |
| width: 200px; |
| margin: 0 1rem; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <svg viewBox="0 0 800 600"> |
| |
| <path id="mainPath" d="M100,300 Q200,100 400,300 T700,300 |
| M100,400 C200,300 600,500 700,400 |
| M100,200 S300,50 400,200 S700,350 700,200" /> |
| |
| |
| <circle class="dot" cx="0" cy="0"> |
| <animateMotion id="dotAnimation" |
| dur="5s" |
| repeatCount="indefinite" |
| path="M100,300 Q200,100 400,300 T700,300 |
| M100,400 C200,300 600,500 700,400 |
| M100,200 S300,50 400,200 S700,350 700,200" /> |
| </circle> |
| </svg> |
| </div> |
|
|
| <div class="controls"> |
| <button id="playPause">Pause</button> |
| <input type="range" id="speed-control" min="0.5" max="3" step="0.1" value="1"> |
| <button id="reverse">Reverse</button> |
| </div> |
|
|
| <script> |
| const dot = document.querySelector('.dot'); |
| const animation = document.getElementById('dotAnimation'); |
| const playPauseBtn = document.getElementById('playPause'); |
| const reverseBtn = document.getElementById('reverse'); |
| const speedControl = document.getElementById('speed-control'); |
| let isPlaying = true; |
| let isReversed = false; |
| |
| playPauseBtn.addEventListener('click', () => { |
| if (isPlaying) { |
| animation.setAttribute('dur', '0s'); |
| playPauseBtn.textContent = 'Play'; |
| } else { |
| animation.setAttribute('dur', `${5 / speedControl.value}s`); |
| playPauseBtn.textContent = 'Pause'; |
| } |
| isPlaying = !isPlaying; |
| }); |
| |
| reverseBtn.addEventListener('click', () => { |
| const path = document.getElementById('mainPath'); |
| const pathData = path.getAttribute('d'); |
| path.setAttribute('d', pathData.split('M').reverse().join('M')); |
| |
| animation.setAttribute('path', path.getAttribute('d')); |
| isReversed = !isReversed; |
| }); |
| |
| speedControl.addEventListener('input', (e) => { |
| if (isPlaying) { |
| animation.setAttribute('dur', `${5 / e.target.value}s`); |
| } |
| }); |
| |
| |
| const path = document.getElementById('mainPath'); |
| const length = path.getTotalLength(); |
| |
| path.style.strokeDasharray = length; |
| path.style.strokeDashoffset = length; |
| |
| path.animate([ |
| { strokeDashoffset: length }, |
| { strokeDashoffset: 0 } |
| ], { |
| duration: 2000, |
| fill: 'forwards', |
| easing: 'ease-in-out' |
| }); |
| </script> |
| </body> |
| </html> |