如何实现水平滚动时按滚动进度逐段绘制SVG圆形
Solution: Animate SVG Circles on Horizontal Scroll
To achieve the effect of drawing one SVG circle for every 20% of horizontal scroll progress, we'll use JavaScript to track scroll position and adjust SVG stroke properties dynamically. Here's a complete, polished implementation:
Modified Code
HTML
<div class="draw-svg-widget"> <div class="svg-circles-progress"> <svg width="421" height="408" viewBox="0 0 421 408" fill="none" xmlns="http://www.w3.org/2000/svg"> <!-- Custom gradient for circle strokes (vertical, matches concentric design) --> <defs> <linearGradient id="circleStrokeGradient" x1="210.5" y1="7" x2="210.5" y2="401" gradientUnits="userSpaceOnUse"> <stop stop-color="#CE008E"/> <stop offset="1" stop-color="#3913BA"/> </linearGradient> <linearGradient id="paint0_linear" x1="424.5" y1="229" x2="-211.5" y2="231" gradientUnits="userSpaceOnUse"> <stop stop-color="#CE008E"/> <stop offset="0.512051" stop-color="#3913BA"/> <stop offset="0.869792" stop-color="#3913BA" stop-opacity="0"/> </linearGradient> </defs> <!-- Concentric circles (ellipses) --> <ellipse cx="209" cy="207" rx="29" ry="29" transform="rotate(-180 209 207)" stroke="#BDBDBD" stroke-dasharray="4 4"/> <ellipse cx="209" cy="207" rx="74" ry="74" transform="rotate(-180 209 207)" stroke="#BDBDBD" stroke-dasharray="4 4"/> <ellipse cx="209" cy="207" rx="118" ry="118" transform="rotate(-180 209 207)" stroke="#BDBDBD" stroke-dasharray="4 4"/> <ellipse cx="209" cy="207" rx="162" ry="162" transform="rotate(-180 209 207)" stroke="#BDBDBD" stroke-dasharray="4 4"/> <ellipse cx="209" cy="207" rx="200" ry="200" transform="rotate(-180 209 207)" stroke="#BDBDBD" stroke-dasharray="4 4"/> <path d="M2.49982 207L2.49982 213.5L15.4998 213.5L15.4998 207L2.49982 207ZM358.637 84.3176C360.913 87.0937 365.009 87.4991 367.785 85.2231C370.561 82.947 370.966 78.8515 368.69 76.0754L358.637 84.3176ZM15.4998 207C15.4998 167.102 27.8331 128.179 50.8107 95.5619L40.183 88.0751C15.6617 122.884 2.49982 164.421 2.49982 207L15.4998 207ZM50.8107 95.5619C73.7883 62.9446 106.287 38.2275 143.856 24.7954L139.479 12.5543C99.3862 26.8888 64.7044 53.2664 40.183 88.0751L50.8107 95.5619ZM143.856 24.7954C181.425 11.3633 222.228 9.87282 260.677 20.5282L264.149 8.00034C223.116 -3.37086 179.572 -1.78029 139.479 12.5543L143.856 24.7954ZM260.677 20.5282C299.126 31.1835 333.341 53.4637 358.637 84.3176L368.69 76.0754C341.695 43.1485 305.181 19.3716 264.149 8.00034L260.677 20.5282Z" fill="url(#paint0_linear)"/> </svg> </div> <div class="horizontal-scroll"> <div class="box one"> Draw Circle 1 </div> <div class="box two"> Draw Circle 2 </div> <div class="box three"> Draw Circle 3 </div> <div class="box four"> Draw Circle 4 </div> <div class="box five"> Draw Circle 5 </div> </div> </div>
CSS
.svg-circles-progress { position: relative; top: 0; left: 0; height: 200px; } .svg-circles-progress svg { max-width: 200px; position: fixed; top: 0; height: 250px; } .horizontal-scroll { margin-top: 50px; width: 1000px; overflow-x: scroll; overflow-y: hidden; height: 200px; border: 1px solid black; display: flex; justify-content: space-between; } .horizontal-scroll .box { margin: 20px; padding: 10px; background: blue; width: 100px; flex-shrink: 0; /* Prevent boxes from shrinking, maintains consistent scroll width */ color: white; text-align: center; }
JavaScript
document.addEventListener('DOMContentLoaded', () => { const scrollContainer = document.querySelector('.horizontal-scroll'); const circles = document.querySelectorAll('.svg-circles-progress ellipse'); // Precompute total stroke length for each circle (one-time calculation) const circleStrokeLengths = Array.from(circles).map(circle => circle.getTotalLength()); // Handle scroll event and update circle states const updateCircleProgress = () => { const totalScrollableWidth = scrollContainer.scrollWidth - scrollContainer.clientWidth; if (totalScrollableWidth === 0) return; // Exit if no scroll is possible // Calculate scroll progress percentage (0-100) const scrollProgress = (scrollContainer.scrollLeft / totalScrollableWidth) * 100; circles.forEach((circle, index) => { const startThreshold = index * 20; const endThreshold = (index + 1) * 20; const strokeLength = circleStrokeLengths[index]; if (scrollProgress < startThreshold) { // Reset to initial dashed gray state circle.style.stroke = '#BDBDBD'; circle.style.strokeDasharray = '4 4'; circle.style.strokeDashoffset = '0'; } else if (scrollProgress >= endThreshold) { // Fully draw the circle with gradient stroke circle.style.stroke = 'url(#circleStrokeGradient)'; circle.style.strokeDasharray = strokeLength; circle.style.strokeDashoffset = '0'; } else { // Partially draw the circle based on current scroll segment const drawPercentage = (scrollProgress - startThreshold) / 20; const drawnLength = strokeLength * drawPercentage; circle.style.stroke = 'url(#circleStrokeGradient)'; circle.style.strokeDasharray = `${drawnLength} ${strokeLength}`; circle.style.strokeDashoffset = '0'; } }); }; // Set initial state on page load updateCircleProgress(); // Attach scroll event listener scrollContainer.addEventListener('scroll', updateCircleProgress); });
Key Details & Improvements
Scroll Progress Logic:
- We calculate scroll progress by dividing the current scroll position by the total scrollable width (content width minus visible width). This gives us a 0-100% value to trigger circle animations.
SVG Stroke Animation:
- Using
getTotalLength()on each circle lets us precisely control how much of the stroke is visible. For partial drawing, we adjust thestrokeDasharrayto show only the completed portion of the circle.
- Using
Gradient Optimization:
- I added a vertical linear gradient (
circleStrokeGradient) that looks more natural on concentric circles than the original horizontal gradient. You can easily modify the colors or direction to match your design.
- I added a vertical linear gradient (
CSS Fix:
- Added
flex-shrink: 0to the scrollable boxes to prevent them from resizing, ensuring the total scrollable width remains consistent across devices.
- Added
This implementation provides a smooth, responsive animation where each circle is drawn incrementally as you scroll through the horizontal container.
内容的提问来源于stack exchange,提问作者Isabella Monza
相关产品推荐
相关产品推荐

