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

如何使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 05:02:03