在DEV社区举办的Summer Bug Smash活动中,开发者分享了一个典型的WebGL内存泄漏案例。这个项目名为FaroIQ,是作者为微软Agents League Hackathon 2026构建的非营利组织战略情报平台,运行在Azure AI Foundry上,由9个Agent组成的流水线可在90秒内生成完整战略报告并自动执行到Microsoft 365。
灯塔成为项目核心视觉
![]()
项目的Hero区域是整个设计中最关键的决策之一。在黑客松比赛中,评委看到的第一屏决定了后续所有印象。FaroIQ在西班牙语中意为"灯塔",作者不想落入每个项目都会用的渐变背景加标题加CTA按钮的俗套,于是用Three.js从零构建了一个完全自定义的3D灯塔:圆柱形塔身分段交替使用石材和条纹材质,灯室用SpotLight和叠加混合锥体实现旋转光束,波浪几何体每帧更新,暗色模式有星粒子、亮色模式有太阳球体,还配置了方向光、点光源、不同密度的雾以及带脉冲透明度的透镜球体。
灯塔并非装饰,它承载了项目概念的核心表达。应用同时具备暗色和亮色模式切换功能,灯塔在两种模式下必须呈现完全不同的视觉效果——不同的雾密度、光束强度、水面颜色、星星出现与消失、月亮与太阳的切换。这意味着组件接收一个isDark属性,并基于它重建整个材质调色板和场景配置。
Bug的多重面孔
这个Bug有多个不一致的表现形式,这让定位变得困难。首次加载时,灯塔有时根本无法完成渲染,浏览器会在Three.js初始化中途停滞,画布保持黑色。在生产环境中,评委第一次打开应用时可能看到Hero区域什么都没有。
当用户点击主题切换按钮时,根据时机和浏览器已使用的资源不同,会发生以下几种情况之一:
- 浏览器控制台出现警告:Too many active WebGL contexts. Oldest context will be lost.(活动WebGL上下文过多,最旧的上下文将丢失)
- 间歇性出现错误:WebGL: INVALID_OPERATION: drawArrays: no buffer is bound to enabled attribute(没有缓冲区绑定到启用的属性)
这个Bug每次都能复现。有时切换一次就出现,有时需要两三次,但最终一定会发生。
问题根源
当useEffect带有依赖项时,React会在任何依赖变化时先运行清理函数,然后重新运行effect。这发生在同一个组件实例内,组件不会卸载,refs保持不变,DOM节点也保持不变。但每次调用new THREE.WebGLRenderer()都会创建新的WebGL上下文,而旧的上下文如果没有被正确释放,就会持续占用GPU内存。
主题切换触发了useEffect重新执行,每次执行都创建新的渲染器实例,但清理函数没有正确调用renderer.dispose()来释放WebGL上下文。多次切换后,浏览器积累了大量未释放的WebGL上下文,最终触发警告并导致渲染失败。
这个案例提醒开发者:在使用Three.js等WebGL库时,渲染器实例的生命周期管理至关重要。特别是在React的useEffect中创建资源时,必须在清理函数中彻底释放,否则内存泄漏会随着用户交互逐渐累积,最终导致应用崩溃或渲染异常。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.