自定义SVG湿度指示器动画异常:无平滑过渡问题求助
自定义湿度指示器组件问题修复
我制作了一个由垂直rectangle和中心circle组成的自定义湿度指示器组件,用于可视化湿度百分比。但遇到问题:circle的颜色变化和移动动画无平滑transition,而是瞬间完成。另外,当前仅设置了湿度最小值30(rectangle的CY加circle的radius),由于SVG左上角为原点的网格特性,动画方向呈现颠倒状态,希望实现类似参考GIF的平滑动画效果。

原始代码
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://svg-cdn.github.io/color-gradient.js"></script> <title>Document</title> </head> <body> <div class="weather-card"> <span class="weather-card-title">Humidity</span> <div class="weather-card-information"> <div class="humidity"> <div class="humidity-data"> <div class="humidity-data-percentage">60</div> <div class="humidity-data-description">Niiiice</div> </div> <div class="humidity-indicator"> <svg-humidity-percentage value=60></svg-humidity-percentage> </div> </div> </div> </div> </body> </html>
CSS
.weather-card { display: flex; flex-direction: column; flex-wrap: wrap; align-content: center; width: 30%; height: 222px; border-radius: 35px; margin: 19px; background: #FFFFFF; position: relative; animation-name: card-animation; animation-duration: 1s; animation-fill-mode: forwards; opacity: 0.2; } .weather-card span.weather-card-title { font-size: larger; font-weight: bold; color: #B6B6B6; margin-top: 20px; margin-left: 20px; flex: 0 0 10%; } .weather-card div.weather-card-information { flex-grow: 1; width: 100%; } div.humidity, div.wind, div.real-temperature-data, div.visibility, div.real-temperature-shade { height: 100%; width: 100%; display: flex; } .humidity-data, .wind-data { display: flex; flex-direction: column; } .humidity-data, .humidity-indicator { width: 50%; } .humidity-data-percentage, .humidity-data-description { height: 50%; } @keyframes card-animation { from { transform: translateY(50px); opacity: 0.2; } to { transform: translateY(0); opacity: 1; } }
JavaScript
customElements.define( "svg-humidity-percentage", class extends HTMLElement { connectedCallback() { let speed = 1.5; this.gradients = new Gradient() .setColorGradient("#4CAF50", "#FFEB3B", "#F44336") .setMidpoint(101) .getColors(); this.style.display = "inline-block"; this.style.height = "80%"; this.percent = this.getAttribute("value")|| "0"; this.innerHTML = `<svg width="50" height="200" viewBox="0 0 50 200"> <rect id="rectangle" x="20" y="20" width="20" height="160" rx="10" ry="10" fill="#ccc" /> <circle id="circle" cx="30" cy="170" r="10" fill="#ff0000"> <animateMotion dur="${speed}s" path="M20 20 H40 V180 H20 Z" keyPoints="0;1" fill="freeze" calcMode="linear" /> </circle> </svg>`; } set percent(val) { document.querySelector('.weather-card').addEventListener('animationend', () => { let gradientIdx = ~~(val / 1); this.setAttribute("value", val); this.querySelector("circle").setAttribute("cy", "30"); this.querySelector("circle").setAttribute("fill", this.gradients[gradientIdx]); this.querySelector("animateMotion").setAttribute("keyPoints", "0;" + val); this.querySelector("animateMotion").beginElement(); }); } } );
修复方案
1. 调整CSS添加颜色过渡
在原有CSS末尾添加:
/* 给圆形添加颜色平滑过渡 */ svg-humidity-percentage circle { transition: fill 1.5s ease; }
2. 修改JavaScript实现平滑动画与方向修正
customElements.define( "svg-humidity-percentage", class extends HTMLElement { connectedCallback() { this.speed = 1.5; this.gradients = new Gradient() .setColorGradient("#4CAF50", "#FFEB3B", "#F44336") .setMidpoint(101) .getColors(); this.style.display = "inline-block"; this.style.height = "80%"; // 替换animateMotion为animate控制cy属性,实现垂直移动 this.innerHTML = `<svg width="50" height="200" viewBox="0 0 50 200"> <rect id="rectangle" x="20" y="20" width="20" height="160" rx="10" ry="10" fill="#ccc" /> <circle id="circle" cx="30" cy="170" r="10" fill="#ff0000"> <animate attributeName="cy" dur="${this.speed}s" fill="freeze" calcMode="linear" /> </circle> </svg>`; // 仅绑定一次卡片动画结束事件,避免重复触发 document.querySelector('.weather-card').addEventListener('animationend', () => { const initialVal = this.getAttribute("value") || "0"; this.percent = initialVal; }); } set percent(val) { const numVal = parseFloat(val); this.setAttribute("value", numVal); const circle = this.querySelector("circle"); const rect = this.querySelector("#rectangle"); // 计算目标cy:湿度越高,圆形越靠上(修正SVG坐标系方向问题) const rectY = parseFloat(rect.getAttribute('y')); const rectHeight = parseFloat(rect.getAttribute('height')); const circleR = parseFloat(circle.getAttribute('r')); const minCy = rectY + circleR; // 顶部(湿度100%) const maxCy = rectY + rectHeight - circleR; // 底部(湿度0%) const targetCy = maxCy - (numVal / 100) * (maxCy - minCy); // 启动移动动画 const animate = circle.querySelector("animate"); animate.setAttribute('from', circle.getAttribute('cy')); animate.setAttribute('to', targetCy); animate.beginElement(); // 根据百分比取对应渐变颜色,配合CSS过渡实现平滑变色 const gradientIdx = Math.max(0, Math.min(100, Math.floor(numVal))); circle.setAttribute("fill", this.gradients[gradientIdx]); } } );
修复说明
- 平滑移动:用
animate元素控制圆形cy属性,替代animateMotion,通过计算修正SVG坐标系方向问题,让湿度越高圆形越靠上。 - 颜色平滑过渡:给圆形添加CSS过渡属性,配合百分比对应渐变颜色,实现颜色的平滑切换。
- 事件优化:将卡片动画结束事件移到组件初始化时绑定,避免重复添加监听导致多次触发。
内容的提问来源于stack exchange,提问作者Starnec
相关产品推荐
相关产品推荐

