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

多页面使用双Canvas时WebGL上下文丢失错误的修复

解决Next.js中页面跳转时WebGL上下文丢失的问题

问题核心

在Next.js+TypeScript项目中,首页使用react-three/fiber的Canvas渲染3D模型,About页使用自定义WebGL Canvas实现交互效果,通过framer-motion的AnimatePresence做导航过渡时,出现错误:WebGL context was lost. loseContext: Already lost.,手动在useEffect返回函数中清理画布无效,对将Canvas移至Layout层的方案存在疑问。

错误原因分析

  1. 自定义WebGL组件的清理逻辑缺陷:当前代码中gl变量疑似全局共享,清理时可能重复调用loseContext;且useEffect依赖为空数组,页面切换时不会触发重新清理,旧上下文未正确释放就创建新上下文。
  2. AnimatePresence的wait模式延迟销毁:mode="wait"会等待退出动画完成后再跳转,旧页面的WebGL组件在过渡期间仍存在,新页面的Canvas创建时可能导致上下文冲突。
  3. 多页面独立Canvas的资源冲突:每个页面单独创建Canvas实例,浏览器对WebGL上下文的数量有限制,频繁创建销毁易导致上下文丢失。

解决方案

1. 修复自定义WebGL组件的清理逻辑

将gl变量封装在组件内部,通过useRef保存,同时添加路由依赖确保页面切换时触发清理:

import { useRef, useEffect } from 'react';
import { usePathname } from 'next/navigation';

const LogoDistort: React.FC<LogoDistortProps> = ({ logoPath }) => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const glRef = useRef<WebGLRenderingContext | null>(null);
  const animationFrameIdRef = useRef<number | undefined>(undefined);
  const pathname = usePathname();
  const particlesRef = useRef<any[]>([]);

  useEffect(() => {
    if (!canvasRef.current) return;
    const canvas = canvasRef.current;
    
    // 初始化WebGL上下文
    const gl = canvas.getContext('webgl');
    if (!gl) return;
    glRef.current = gl;

    particlesRef.current = [];
    setupWebGL(canvas, gl); // 修改setupWebGL,传入gl参数
    setupShaders(gl); // 传入gl
    loadImg(canvas, logoPath, gl); // 传入gl
    setupEvents(canvas, logoPath, gl); // 传入gl

    return () => {
      // 正确清理顺序
      if (animationFrameIdRef.current) {
        cancelAnimationFrame(animationFrameIdRef.current);
        animationFrameIdRef.current = undefined;
      }

      // 销毁WebGL资源(着色器、缓冲区等)
      if (glRef.current) {
        // 示例:销毁着色器程序(需根据实际代码调整)
        // if (programRef.current) glRef.current.deleteProgram(programRef.current);
        // 清理所有缓冲区、纹理等资源
        
        const ext = glRef.current.getExtension("WEBGL_lose_context");
        if (ext) {
          ext.loseContext();
        }
        glRef.current = null;
      }

      particlesRef.current = [];
    };
  }, [pathname, logoPath]); // 添加路由和logoPath作为依赖

  return <canvas ref={canvasRef} className="canvas" />;
};

export default LogoDistort;

2. 实现Canvas移至Layout层的方案

Next.js的Layout组件是全局共享的,将WebGL Canvas放在Layout中,根据当前路由切换渲染内容,避免频繁创建销毁上下文:

步骤1:创建全局WebGL容器组件

// components/GlobalWebGL.tsx
'use client';
import { usePathname } from 'next/navigation';
import EarthScene from './EarthScene'; // 首页的r3f场景组件
import LogoDistort from './LogoDistort'; // About页的自定义WebGL组件

const GlobalWebGL = () => {
  const pathname = usePathname();

  return (
    <div className="fixed inset-0 z-0">
      {pathname === '/' && <EarthScene />}
      {pathname === '/about' && <LogoDistort logoPath="/your-logo.png" />}
    </div>
  );
};

export default GlobalWebGL;

步骤2:在Layout中引入全局WebGL组件

// app/layout.tsx
import GlobalWebGL from '@/components/GlobalWebGL';
import Navbar from '@/components/Navbar';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="zh-CN">
      <body>
        <GlobalWebGL />
        <Navbar />
        <main className="relative z-10">{children}</main>
      </body>
    </html>
  );
}

这样所有页面共享同一个Canvas容器,路由切换时仅切换渲染的场景,从根源上避免上下文频繁创建销毁的问题。

3. 调整AnimatePresence的导航逻辑

确保旧页面的WebGL组件在跳转前完全销毁,修改导航按钮的点击逻辑,先关闭菜单再跳转:

// Navbar组件中的点击函数
const clickHome = () => {
  setMenuOpen(false);
  // 等待菜单退出动画完成后再跳转
  setTimeout(() => {
    router.push('/');
  }, 300); // 对应菜单exit动画的duration
};

const clickAbout = () => {
  setMenuOpen(false);
  setTimeout(() => {
    router.push('/about');
  }, 300);
};

同时可移除AnimatePresence的mode="wait",避免延迟跳转导致的上下文冲突:

<AnimatePresence
  onExitComplete={() => {
    if (pendingRoute) {
      router.push(pendingRoute);
      setPendingRoute(null);
    }
  }}
>
  {/* 菜单内容 */}
</AnimatePresence>

关键注意事项

  • 避免使用全局的WebGL上下文变量,每个组件实例应独立管理自己的上下文。
  • 清理WebGL资源时,要先销毁着色器、缓冲区等资源,再释放上下文。
  • 全局Canvas方案适合多页面包含WebGL内容的项目,能有效减少上下文资源冲突。

内容的提问来源于stack exchange,提问作者Athul Kesav

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 18:44:51