求助:使用GSAP复刻Flecto.io/About页面的流体盒子动画
我正在尝试用GSAP复刻Flecto.io关于页的盒子动画效果,做了一个CodePen但还没达到理想的流畅度。我是GSAP新手,不确定是不是必须用MorphSVGPlugin,总感觉自己可能把问题复杂化了,现在的动画和原网站比起来生硬很多,实在找不到问题出在哪,有没有熟悉GSAP(尤其是MorphSVGPlugin)的大佬能给我指指路?我这个方向对吗,还是应该换一种实现思路?
我的代码如下:
HTML代码
<body> <div class="about-header"> <div class="ballons-container row" data-v-1a56d31c=""> <div class="mission-ballon ballon mission" data-v-12007639="" data-v-1a56d31c=""> <svg id="missionSvg" data-flectofier-svg="" viewBox="0 0 730.5390625 529.9140625" data-v-12007639=""> <rect id="smallRect" x="586.953125" y="0" width="143.585938" height="59" fill="#01382c"></rect> <rect id="bigRect" x="0" y="51.65625" width="594.296875" height="0" fill="#01382c"></rect> <path id="finalPath" d="M17.294117647058822 51.65625 L569.6590073529412 51.65625 C569.6590073529412 51.65625, 586.953125 51.65625, 586.953125 34.362132352941174 L586.953125 17.294117647058822 C586.953125 17.294117647058822, 586.953125 0, 604.2472426470588 0 L713.2449453529412 0 C713.2449453529412 0, 730.5390629999999 0, 730.5390629999999 17.294117647058822 L730.5390629999999 41.705882352941174 C730.5390629999999 41.705882352941174, 730.5390629999999 59, 713.2449453529412 59 L611.5909926470588 59 C611.5909926470588 59, 594.296875 59, 594.296875 76.29411764705883 L594.296875 76.29411764705883 C594.296875 76.29411764705883, 594.296875 93.58823529411765, 577.0027573529412 93.58823529411765 L17.294117647058822 93.58823529411765 C17.294117647058822 93.58823529411765, 0 93.58823529411765, 0 76.29411764705883 L0 68.95036764705883 C0 68.95036764705883, 0 51.65625, 17.294117647058822 51.65625" fill="#01382c" style="opacity: 0;"></path> </svg> <div class="small-container" data-v-12007639=""> <span class="label" data-v-12007639="">Mission</span> </div> <div class="big-container" data-v-12007639=""> <span class="label" data-v-12007639=""> We support a new generation of people and businesses in redefining consumer habits... </span> </div> </div> </div> </div> </body>
CSS代码
<style> .mission-ballon [data-flectofier-svg][data-v-12007639] { bottom: 0; left: 0; position: absolute; right: 0; top: 0; z-index: 0; } .mission-ballon [data-flectofier-svg] [data-flectofier-svg-input][data-v-12007639] { display: none; } .mission-ballon [data-flectofier-svg] [data-flectofier-svg-output][data-v-12007639] { fill: #01382c; } .about-header .ballons-container[data-v-1a56d31c] { --col-number: 16; --gutter: 1.5rem; display: block; grid-column: 1; grid-row: 1; padding-top: 100px; position: relative; width: 100%; } .about-header .ballons-container[data-v-1a56d31c] .ballon { max-width: 100vw; } .about-header .ballons-container .mission[data-v-1a56d31c] { --col-offset: 9; flex: none; left: calc(9 / var(--col-number) * 100% + var(--gutter) * .5); left: calc(var(--col-offset) / var(--col-number) * 100% + var(--gutter) * .5); transform: translateX(calc(-100% + var(--gutter))); } .mission-ballon[data-v-12007639] { --overlap: 0.5vw; display: grid; position: relative; width: -moz-fit-content; width: fit-content; cursor: pointer; } .mission-ballon .small-container[data-v-12007639] { grid-column: 2; grid-row: 1; margin-bottom: calc(var(--overlap) * -1); margin-left: calc(var(--overlap) * -1); padding: max(10px, min(calc(7.0229px + .76336vw), 20px)) max(20px, min(calc(12.25954px + 1.98473vw), 46px)); position: relative; } .about-header .ballons-container[data-v-1a56d31c] .ballon .small-container .label { font-size: max(16px, min(calc(12.97784px + .29542vw), 18px)); letter-spacing: .02em; line-height: 1.3; } .mission-ballon .big-container[data-v-12007639] { grid-column: 1; grid-row: 2; max-width: max(350px, min(calc(-209.09897px + 54.65288vw), 720px)); padding: max(80px, min(calc(19.55687px + 5.90842vw), 120px)) max(35px, min(calc(-32.99852px + 6.64697vw), 80px)) max(30px, min(calc(-22.88774px + 5.16987vw), 65px)); position: relative; } .about-header .ballons-container[data-v-1a56d31c] .ballon .big-container .label { font-size: max(32px, min(calc(-16.35451px + 4.72674vw), 64px)); font-variation-settings: "wght" 500; letter-spacing: -.03em; line-height: 1; } .mission-ballon .big-container[data-v-12007639] { opacity: 0; transition: opacity 0.6s ease; } .mission-ballon .big-container .label[data-v-12007639] { opacity: 0; transition: opacity 0.6s ease 0.2s; } .mission-ballon .small-container .label[data-v-12007639], .mission-ballon .big-container .label[data-v-12007639] { color: white; } </style>
JS代码
import gsapTrial from "https://esm.sh/gsap-trial"; import greensock from "https://esm.sh/greensock"; import gsap from "https://esm.sh/gsap"; document.addEventListener("DOMContentLoaded", function () { setTimeout(startAnimation, 1000); }); function startAnimation() { console.log("Starting animation..."); const smallRect = document.getElementById("smallRect"); const bigRect = document.getElementById("bigRect"); const finalPath = document.getElementById("finalPath"); const smallLabel = document.querySelector(".mission-ballon .small-container .label"); const bigContainer = document.querySelector(".mission-ballon .big-container"); const bigLabel = document.querySelector(".mission-ballon .big-container .label"); gsap.set([smallLabel, bigContainer, bigLabel], { opacity: 0 }); gsap.set(finalPath, { opacity: 0 }); const tl = gsap.timeline({ defaults: { ease: "power2.inOut" } }); tl.to(smallLabel, { opacity: 1, duration: 0.6 }); tl.to(bigRect, { height: 478, duration: 1.2 }, "+=0.4"); tl.to([smallRect, bigRect], { duration: 1, morphSVG: { shape: finalPath, type: "linear" }, onStart: () => gsap.set(finalPath, { opacity: 1 }), onComplete: () => gsap.set([smallRect, bigRect], { opacity: 0 }), }, "-=0.4"); tl.to(bigContainer, { opacity: 1, duration: 0.8 }, "-=0.2"); tl.to(bigLabel, { opacity: 1, duration: 0.8 }, "-=0.4"); }
我的解决方案与思路
你方向是对的,MorphSVGPlugin确实是实现这种平滑形状过渡的核心工具,问题主要出在插件注册、动画时间线衔接和过渡参数上,我给你调整几个关键细节:
正确引入并注册MorphSVGPlugin
你当前的代码没注册MorphSVGPlugin,导致morphSVG效果无法正常工作。在CodePen里可以直接导入并注册:import gsap from "https://esm.sh/gsap"; import MorphSVGPlugin from "https://esm.sh/gsap/MorphSVGPlugin"; gsap.registerPlugin(MorphSVGPlugin);优化形状过渡的参数与时间线
两个矩形同时过渡到路径时,linear类型的morph可能因为形状锚点差异导致生硬,换成rotational能让过渡路径更自然;同时调整时间线的偏移,让形状过渡和容器淡入的节奏更贴合原网站:const tl = gsap.timeline({ defaults: { ease: "expo.inOut" } }); tl.to(smallLabel, { opacity: 1, duration: 0.6 }) .to(bigRect, { height: 478, duration: 1.2 }, "+=0.2") // 让morph动画在bigRect高度动画接近完成时启动 .set(finalPath, { visibility: "visible" }, "-=0.3") .to([smallRect, bigRect], { duration: 1.4, morphSVG: { shape: finalPath, type: "rotational" }, onComplete: () => gsap.set([smallRect, bigRect], { visibility: "hidden" }) }, "-=0.3") .to(bigContainer, { opacity: 1, duration: 0.8 }, "-=0.5") .to(bigLabel, { opacity: 1, duration: 0.8 }, "-=0.6");优化SVG元素的显示状态
用visibility替代opacity控制SVG元素的显示隐藏,能避免过渡过程中出现不必要的闪烁,因为opacity: 0元素仍会占据布局并参与渲染,而visibility: hidden会跳过渲染。参考原网站的动画节奏
可以用GSAP DevTools插件查看原网站的时间线细节,包括每个动画的时长、ease类型,这样能更精准地复刻流畅度。
调整后你的动画应该就能接近原网站的流体效果了,要是还有细节问题,随时再问!
备注:内容来源于stack exchange,提问作者Sleepylala

