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

如何实现可变宽度SVG路径的流畅填充动画(GSAP+clipPath)

解决方案:无瑕疵的SVG填充流动动画

针对你遇到的stroke-width无法适配Logo宽窄区域的问题,以下是几种可靠的替代方案,从易实现到高精度排序:

1. 蒙版(Mask)+ 渐变位移动画(最推荐,易实现且无瑕疵)

放弃stroke模拟填充的思路,改用基础填充形状+动态蒙版的方式,完全贴合字母轮廓,不受路径宽窄影响:

实现步骤:

  • 为每个字母部件创建两个核心元素:
    • 一个与字母内部区域完全匹配的基础填充形状(设置最终的填充色);
    • 一个覆盖字母区域的蒙版元素,用渐变(线性/径向)作为填充,渐变包含「完全透明」和「完全不透明」的停止点。
  • 用GSAP动画渐变的位置或渐变停止点的偏移量,让不透明区域沿着预设路径逐步覆盖基础填充形状,模拟流动填充效果。

代码示例:

<svg viewBox="0 0 200 100">
  <!-- 字母M的基础填充形状 -->
  <path id="m-fill" d="M20,80 L30,20 L40,80 L50,20 L60,80 Z" fill="#ff4444"/>
  
  <!-- 蒙版:用渐变控制填充范围 -->
  <mask id="m-mask">
    <linearGradient id="flow-gradient" x1="20" y1="80" x2="20" y2="80">
      <stop offset="0%" stop-color="#fff"/> <!-- 不透明,显示填充 -->
      <stop offset="0%" stop-color="#000"/> <!-- 透明,隐藏填充 -->
    </linearGradient>
    <rect x="20" y="20" width="40" height="60" fill="url(#flow-gradient)"/>
  </mask>
  
  <!-- 应用蒙版的最终显示元素 -->
  <use href="#m-fill" mask="url(#m-mask)"/>
  
  <!-- 预设的流动引导路径(可隐藏) -->
  <path id="flow-path" d="M20,80 C30,50 40,50 50,20 L60,80" stroke="red" stroke-width="1" fill="none"/>
</svg>

<script>
// 用GSAP让渐变终点沿引导路径运动
gsap.to("#flow-gradient", {
  motionPath: {
    path: "#flow-path",
    align: "#flow-path",
    autoRotate: true
  },
  duration: 2.5,
  ease: "power2.inOut",
  // 同步动画渐变停止点的偏移量,让过渡更自然
  onUpdate: function() {
    const progress = this.progress();
    gsap.set("#flow-gradient stop:last-child", { attr: { offset: `${progress * 100}%` } });
  }
});
</script>

2. 形状补间(MorphSVG)+ 裁剪路径

如果需要更精准的填充边界(比如流动前沿是硬边缘而非渐变过渡),可以用形状生长的方式:

实现步骤:

  • 将每个字母的填充形状拆分为「初始空形状」和「完整填充形状」,初始形状仅包含流动起点的一小段,完整形状是整个字母内部区域。
  • 用GSAP的MorphSVGPlugin动画两个形状的d属性,实现填充区域从起点逐步扩展到整个字母的效果。
  • 配合clipPath限制显示范围,确保形状生长完全贴合字母轮廓。

关键代码片段:

// 初始化MorphSVG插件
gsap.registerPlugin(MorphSVGPlugin);

// 动画形状从初始状态过渡到完整状态
gsap.to("#initial-shape", {
  morphSVG: "#full-shape",
  duration: 2,
  ease: "power1.inOut"
});

3. 原方案优化:动态调整stroke-width

如果坚持使用原有的stroke模拟方案,可以通过GSAP的实时回调动态调整stroke-width,适配路径上的宽窄区域:

实现步骤:

  • 将引导路径按宽窄区域拆分为多个子路径,或在动画过程中实时获取路径上的点。
  • 计算当前路径点的曲率(曲率越大,区域越窄),动态设置对应子路径的stroke-width。
  • 同步所有子路径的strokeDashoffset动画进度,保证流动效果一致。

关键代码片段:

const path = document.querySelector(".flow-path");
const totalLength = path.getTotalLength();

gsap.to(path, {
  strokeDashoffset: 0,
  duration: 2,
  ease: "linear",
  onUpdate: function() {
    const progress = this.progress();
    const currentLength = totalLength * progress;
    const point = path.getPointAtLength(currentLength);
    // 计算当前点的曲率(简化示例,实际需用路径二阶导数计算)
    const curvature = getCurvatureAtPoint(path, currentLength);
    // 曲率越大,stroke-width越小
    const strokeWidth = Math.max(2, 20 / (curvature + 1));
    gsap.set(path, { strokeWidth });
  }
});

// 辅助函数:计算路径某点的曲率
function getCurvatureAtPoint(path, length) {
  const p0 = path.getPointAtLength(Math.max(0, length - 5));
  const p1 = path.getPointAtLength(length);
  const p2 = path.getPointAtLength(Math.min(totalLength, length + 5));
  // 用三点计算曲率(简化公式)
  const dx1 = p1.x - p0.x;
  const dy1 = p1.y - p0.y;
  const dx2 = p2.x - p1.x;
  const dy2 = p2.y - p1.y;
  const cross = dx1 * dy2 - dy1 * dx2;
  const len1 = Math.sqrt(dx1*dx1 + dy1*dy1);
  const len2 = Math.sqrt(dx2*dx2 + dy2*dy2);
  return Math.abs(cross) / (len1 * len2 * (len1 + len2));
}

内容的提问来源于stack exchange,提问作者Maximus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 16:57:33