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

单页应用路由跳转时Curtainjs不生效 刷新后正常

问题现象

在网站中使用curtainjs实现WebGL平面滚动变形效果时,从落地页通过路由跳转到关于页,curtainjs无法正常工作,手动刷新页面后所有功能均可正常运行。
当前将初始化逻辑绑定在组件mounted生命周期中,暂未找到更合适的调用位置,现有实现代码如下:

mounted(){
    window.addEventListener("load", this.curtainjs);
}
methods: {
   curtainjs() {
      const curtains = new Curtains({
        container: "canvas",
        pixelRatio: Math.min(1.5, window.devicePixelRatio),
      });

      curtains
        .onRender(() => {
          // 更新平面变形参数
          planesDeformations = curtains.lerp(planesDeformations, 0, 0.075);
        })
        .onScroll(() => {
          // 获取滚动差值应用滚动效果
          const delta = curtains.getScrollDeltas();

          // 反转y轴值适配效果
          delta.y = -delta.y;

          // 设置阈值
          if (delta.y > 60) {
            delta.y = 60;
          } else if (delta.y < -60) {
            delta.y = -60;
          }

          if (Math.abs(delta.y) > Math.abs(planesDeformations)) {
            planesDeformations = curtains.lerp(
              planesDeformations,
              delta.y,
              0.1
            );
          }
        })
        .onError(() => {
          // 给body添加类显示原始图片
          document.body.classList.add("no-curtains", "planes-loaded");
        })
        .onContextLost(() => {
          // 上下文丢失时尝试恢复
          curtains.restoreContext();
        });

      const planes = [];
      let planesDeformations = 0;

      // 获取所有平面元素
      let planeElements = document.getElementsByClassName("plane");

      const vs = `
        precision mediump float;
    
        // 默认内置变量
        attribute vec3 aVertexPosition;
        attribute vec2 aTextureCoord;
    
        uniform mat4 uMVMatrix;
        uniform mat4 uPMatrix;
    
        uniform mat4 planeTextureMatrix;
    
        // 自定义变量
        varying vec3 vVertexPosition;
        varying vec2 vTextureCoord;
    
        uniform float uPlaneDeformation;
    
        void main() {
            vec3 vertexPosition = aVertexPosition;
    
            // 滚动变形效果
            vertexPosition.y += sin(((vertexPosition.x + 1.0) / 2.0) * 3.141592) * (sin(uPlaneDeformation / 90.0));
    
            gl_Position = uPMatrix * uMVMatrix * vec4(vertexPosition, 1.0);
    
            // 传递易变变量
            vVertexPosition = vertexPosition;
            vTextureCoord = (planeTextureMatrix * vec4(aTextureCoord, 0.0, 1.0)).xy;
        }
    `;

      const fs = `
        precision mediump float;
    
        varying vec3 vVertexPosition;
        varying vec2 vTextureCoord;
    
        uniform sampler2D planeTexture;
    
        void main() {
            // 直接渲染纹理
            gl_FragColor = texture2D(planeTexture, vTextureCoord);
        }
    `;

      // 所有平面共用参数
      const params = {
        vertexShader: vs,
        fragmentShader: fs,
        widthSegments: 10,
        heightSegments: 10,
        fov: 45,
        drawCheckMargins: {
          top: 0,
          right: 0,
          bottom: 0,
          left: 0,
        },
        uniforms: {
          planeDeformation: {
            name: "uPlaneDeformation",
            type: "1f",
            value: 0,
          },
        },
      };

      // 添加平面并处理
      for (let i = 0; i < planeElements.length; i++) {
        planes.push(new Plane(curtains, planeElements[i], params));

        handlePlanes(i);
      }

      // 平面处理逻辑
      function handlePlanes(index) {
        const plane = planes[index];

        plane
          .onReady(() => {
            // 所有资源加载完成后显示内容
            if (index === planes.length - 1) {
              document.body.classList.add("planes-loaded");
              document.querySelectorAll(".picture").forEach((img) => {
                img.style.display = "none";
              });
            }
          })
          .onRender(() => {
            // 更新uniform变量
            plane.uniforms.planeDeformation.value = planesDeformations;
          });
      }
    },
}
故障原因
  • 核心问题是单页应用路由跳转时window.load事件早已触发完成,mounted中绑定的load事件监听永远不会执行,curtainjs根本没有完成初始化。只有手动刷新页面时,load事件会在页面资源加载完成后正常触发,所以功能表现正常。
  • 代码存在变量声明顺序问题:planesDeformations变量在Curtains实例的render、scroll回调绑定之后才声明,虽然JS有变量提升,但初始化时机不对容易引发访问异常。
  • 缺少实例销毁逻辑:路由切换离开当前页面时,没有销毁已创建的Curtains实例、移除事件监听,多次路由跳转后会出现WebGL上下文冲突、内存泄漏问题。
修复方案
  • 移除window.load事件绑定,在mounted中通过$nextTick确保DOM渲染完成后直接调用curtainjs初始化方法,不需要等待window load事件。
  • 调整变量声明顺序,将planes、planesDeformations变量放到Curtains实例创建之前声明,保证回调执行时变量可正常访问。
  • 增加组件卸载生命周期逻辑,离开页面时销毁Curtains实例,清理相关副作用。

修复后的核心代码参考:

data() {
  return {
    curtains: null,
    planes: [],
    planesDeformations: 0
  }
},
mounted(){
  // DOM渲染完成后直接初始化
  this.$nextTick(() => {
    this.curtainjs()
  })
},
beforeUnmount() { // Vue2框架替换为beforeDestroy
  // 销毁实例清理副作用
  if(this.curtains) {
    this.planes.forEach(plane => plane.remove())
    this.curtains.dispose()
  }
},
methods: {
   curtainjs() {
      // 变量提前初始化
      this.planes = []
      this.planesDeformations = 0

      this.curtains = new Curtains({
        container: "canvas",
        pixelRatio: Math.min(1.5, window.devicePixelRatio),
      });

      this.curtains
        .onRender(() => {
          this.planesDeformations = this.curtains.lerp(this.planesDeformations, 0, 0.075);
        })
        .onScroll(() => {
          const delta = this.curtains.getScrollDeltas();
          delta.y = -delta.y;

          if (delta.y > 60) delta.y = 60;
          else if (delta.y < -60) delta.y = -60;

          if (Math.abs(delta.y) > Math.abs(this.planesDeformations)) {
            this.planesDeformations = this.curtains.lerp(
              this.planesDeformations,
              delta.y,
              0.1
            );
          }
        })
        .onError(() => {
          document.body.classList.add("no-curtains", "planes-loaded");
        })
        .onContextLost(() => {
          this.curtains.restoreContext();
        });

      let planeElements = document.getElementsByClassName("plane");
      // 顶点着色器、片元着色器、平面参数定义和原有逻辑一致
      // ...

      for (let i = 0; i < planeElements.length; i++) {
        const plane = new Plane(this.curtains, planeElements[i], params);
        this.planes.push(plane);
        this.handlePlanes(i);
      }
    },
    handlePlanes(index) {
      const plane = this.planes[index];
      plane
        .onReady(() => {
          if (index === this.planes.length - 1) {
            document.body.classList.add("planes-loaded");
            document.querySelectorAll(".picture").forEach((img) => {
              img.style.display = "none";
            });
          }
        })
        .onRender(() => {
          plane.uniforms.planeDeformation.value = this.planesDeformations;
        });
    }
}

如果canvas容器、.plane元素是异步渲染的,需要等对应数据加载完成、DOM更新后再调用初始化方法。

内容的提问来源于stack exchange,提问作者Eli-ott

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 23:00:57