You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 03:47:01