单页应用路由跳转时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
相关产品推荐
相关产品推荐

