如何使用Webgi与Gsap实现Canvas动画及滚动触发定位控制
问题描述
- 需求:使用Webgi与Gsap为Canvas内的模型添加滚动触发器。画布进入视口时,模型处于指定X/Y/Z轴位置;滚动时触发模型缩放到目标X'/Y'/Z'位置,保持该状态200vh滚动距离后切换到其他组件。
- 当前问题:无法正确设置模型在画布中的位置,不知道如何让画布在200vh滚动期间保持状态。
参考代码:Wrapper组件
export default function Wrapper() { const canvasRef = useRef(null); const memoizedScrollAnimatiom = useCallback((position, target, onUpdate) => { if (position && onUpdate && target) { ScrollAnimation(position, target, onUpdate); } }, []); const setupViewer = useCallback(async () => { const viewer = new ViewerApp({ canvas: canvasRef.current, }); const manager = await viewer.addPlugin(AssetManagerPlugin); const camera = viewer.scene.activeCamera; var position = camera.position; const target = camera.target; await viewer.addPlugin(GBufferPlugin); await viewer.addPlugin(new ProgressivePlugin(32)); await viewer.addPlugin(new TonemapPlugin(true)); await viewer.addPlugin(GammaCorrectionPlugin); await viewer.addPlugin(SSRPlugin); await viewer.addPlugin(SSAOPlugin); await viewer.addPlugin(BloomPlugin); console.log(position); viewer.renderer.refreshPipeline(); var needsUpdate = true; const onUpdate = () => { needsUpdate = true; viewer.setDirty(); }; await manager.addFromPath("../../assets/Desktop/scene.glb"); viewer.getPlugin(TonemapPlugin).config.clifBackground = true; viewer.scene.activeCamera.setCameraOptions({ controlsEnabled: false }); viewer.addEventListener("preFrame", () => { if (needsUpdate) { camera.positionUpdated(true); needsUpdate = false; } }); memoizedScrollAnimatiom(position, target, onUpdate); console.log(position); }, []); useEffect(() => { setupViewer(); }, []); return ( <Section> <div id="webGi"> <canvas id="webGi-canvas" className="webgiCanva" ref={canvasRef} ></canvas> </div> <div className="gradient-background"> <Hero/> </div> </Section> ); }
GSAP动画文件代码
import gsap from "gsap"; export const ScrollAnimation = (position, target, onUpdate) => { const tl1 = gsap.timeline(); tl1 .to(position, { x: 8.7109187797, y: 1.1866802099, z: -0.2344851352, scrollTrigger: { trigger: ".webgiCanva", start: "top bottom", end: "bottom top", scrub: 2, immediateRender: false, }, onUpdate, }) .to(target, { x: 0, y: 0, z: 0, scrollTrigger: { trigger: ".webgiCanva", start: "top bottom", end: " bottom top", scrub: 2, immediateRender: false, }, }); };
解决方案
1. 正确设置模型初始位置
当前代码修改的是相机位置而非模型位置,需根据需求调整:
场景1:直接控制模型位置
在setupViewer函数中,加载模型后保存实例并设置初始位置:
// 加载模型并获取实例 const model = await manager.addFromPath("../../assets/Desktop/scene.glb"); // 设置模型初始位置与缩放 model.position.set(初始X值, 初始Y值, 初始Z值); model.scale.set(初始缩放X, 初始缩放Y, 初始缩放Z);
场景2:通过相机移动模拟模型位置变化
如果是通过相机视角变化实现视觉上的模型位置改变,需提前设置相机初始状态:
// 设置相机初始位置与目标点 camera.position.set(初始相机X, 初始相机Y, 初始相机Z); camera.target.set(初始目标X, 初始目标Y, 初始目标Z); // 通知webgi更新相机状态 camera.positionUpdated(true);
2. 实现200vh滚动期间保持状态
通过GSAP ScrollTrigger的相对范围配置,将动画分为「滚动到目标状态」和「保持状态」两个阶段:
修改GSAP动画函数
import gsap from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; gsap.registerPlugin(ScrollTrigger); export const ScrollAnimation = (model, camera, onUpdate) => { // 以模型缩放为例,可替换为相机位置控制 const tl = gsap.timeline({ scrollTrigger: { trigger: ".webgiCanva", start: "top bottom", // 画布顶部进入视口时触发 end: "+=200vh", // 从触发点开始滚动200vh后结束 scrub: 2, immediateRender: false, } }); // 阶段1:前100vh滚动时,从初始状态动画到目标状态 tl.to(model.scale, { x: 目标缩放X, y: 目标缩放Y, z: 目标缩放Z, duration: 1, // 对应100vh滚动距离 onUpdate }) // 阶段2:后100vh滚动时,保持目标状态不变 .to({}, { duration: 1 }); // 空动画占满剩余滚动范围 // 若控制相机,可替换为以下代码 // tl.to(camera.position, { // x: 目标相机X, // y: 目标相机Y, // z: 目标相机Z, // duration: 1, // onUpdate // }) // .to(camera.target, { // x: 目标相机目标X, // y: 目标相机目标Y, // z: 目标相机目标Z, // duration: 1, // }) // .to({}, { duration: 1 }); };
确保容器高度足够
给画布容器设置最小高度,支撑200vh的滚动范围:
#webGi { min-height: 300vh; /* 初始视口高度 + 200vh滚动范围 */ }
3. 其他优化点
- 必须注册ScrollTrigger插件:在GSAP文件中导入并注册
ScrollTrigger。 - 确保模型加载完成后再执行动画:在
await manager.addFromPath之后调用动画函数,避免操作未加载的模型。 - 避免重复创建触发器:
memoizedScrollAnimatiom的依赖数组保持为空,确保只执行一次动画初始化。
内容的提问来源于stack exchange,提问作者Oculus
相关产品推荐
相关产品推荐

