HTML5幻灯片中如何停止Three.js地形网格旋转动画?
解决Three.js地形动画无法停止及GPU占用过高的问题
看起来你遇到了两个核心问题:cancelAnimationFrame没真正终止动画循环,以及删除DOM元素后GPU仍持续高占用。我们一步步来拆解解决:
问题根源分析
1. cancelAnimationFrame失效的原因
你只保存了第一次调用requestAnimationFrame(animate)的ID,但在animate函数内部,每次递归调用requestAnimationFrame(animate)时,新的帧请求ID并没有更新到全局变量id_an里。这导致你最后取消的只是第一帧的请求,后续的动画循环还在默默执行。
2. GPU占用过高的原因
即使删除了DOM元素,Three.js创建的WebGL上下文、场景、网格等资源仍然驻留在内存中,GPU还在持续处理这些资源的渲染逻辑,所以占用率不会下降。必须主动销毁这些Three.js资源才能释放GPU。
修改后的完整解决方案
我们需要做这几个关键调整:
- 在
animate函数里更新全局的id_an,保存最新的帧请求ID - 停止动画时,不仅取消帧请求,还要彻底销毁Three.js的所有相关资源
- 清理全局变量,避免内存泄漏
下面是修改后的代码:
// 全局变量保存关键资源,方便后续清理 var id_an; var renderer, scene, mesh; function run_land(){ // 初始化前先清理旧资源,防止重复调用导致内存泄漏 if (end_landscapes) end_landscapes(); renderer = new THREE.WebGLRenderer({ antialias : true }); renderer.setSize( window.innerWidth+600, window.innerHeight ); var the_dom = renderer.domElement; the_dom.id = 'land_id'; document.getElementById('front_id').appendChild(the_dom); var onRenderFcts= []; scene = new THREE.Scene(); var camera = new THREE.PerspectiveCamera(25, window.innerWidth/window.innerHeight, 0.01, 1000); camera.position.z = 15; camera.position.y = 2; scene.fog = new THREE.Fog(0x565656, 10, 45); // 灯光逻辑保持不变 (function(){ var light = new THREE.AmbientLight( 0x202020 ); scene.add( light ); var light = new THREE.DirectionalLight('white', 5); light.position.set(0.5, 0.0, 2); scene.add( light ); var light = new THREE.DirectionalLight('white', 0.75*2); light.position.set(-0.5, -0.5, -2); scene.add( light ); })() var heightMap = THREEx.Terrain.allocateHeightMap(512,256); THREEx.Terrain.simplexHeightMap(heightMap); var geometry = THREEx.Terrain.heightMapToPlaneGeometry(heightMap); THREEx.Terrain.heightMapToVertexColor(heightMap, geometry); var material = new THREE.MeshBasicMaterial({ wireframe: true }); mesh = new THREE.Mesh( geometry, material ); mesh.name = 'the_mesh_name'; scene.add( mesh ); mesh.lookAt(new THREE.Vector3(0,1,0)); mesh.scale.y = 4.5; mesh.scale.x = 5; mesh.scale.z = 0.30; mesh.scale.multiplyScalar(10); onRenderFcts.push(function(delta, now){ mesh.rotation.z += 0.2 * delta; }) onRenderFcts.push(function(){ renderer.render( scene, camera ); }) var lastTimeMsec= null; function animate(nowMsec){ // 保存最新的帧请求ID到全局变量 id_an = requestAnimationFrame( animate ); lastTimeMsec = lastTimeMsec || nowMsec-1000/60; var deltaMsec = Math.min(200, nowMsec - lastTimeMsec); lastTimeMsec = nowMsec; onRenderFcts.forEach(function(onRenderFct){ onRenderFct(deltaMsec/5000, nowMsec/5000); }); } // 启动动画时保存第一帧ID id_an = requestAnimationFrame(animate); } var end_landscapes = (function() { return function() { console.log("停止动画,当前帧ID: "+id_an); // 取消当前的帧请求 if (id_an) { cancelAnimationFrame(id_an); id_an = null; } // 清理Three.js资源,释放GPU if (mesh) { // 释放几何体和材质的GPU资源 mesh.geometry.dispose(); mesh.material.dispose(); scene.remove(mesh); mesh = null; } if (scene) { // 清空场景所有子对象 while(scene.children.length > 0){ scene.remove(scene.children[0]); } scene = null; } if (renderer) { // 销毁渲染器,释放WebGL上下文 renderer.dispose(); // 从DOM中移除渲染器元素 var domElement = renderer.domElement; if (domElement && domElement.parentNode) { domElement.parentNode.removeChild(domElement); } renderer = null; } }; })();
关键修改点说明
- 更新帧请求ID:在
animate函数内部,每次调用requestAnimationFrame时都把返回的ID赋值给id_an,这样cancelAnimationFrame就能拿到最新的帧ID,彻底终止动画循环。 - 资源销毁逻辑:
- 调用
geometry.dispose()和material.dispose()释放几何体和材质占用的GPU资源 - 从场景中移除网格并置空,避免内存引用残留
- 清空场景所有子对象,确保没有遗漏的资源
- 调用
renderer.dispose()销毁渲染器,这是释放WebGL上下文、降低GPU占用的核心步骤 - 从DOM中移除渲染器的canvas元素,彻底清理页面残留
- 调用
- 重复调用防护:在
run_land开头先调用end_landscapes,防止重复创建资源导致的内存泄漏。
这样修改后,当你切换幻灯片调用end_landscapes()时,不仅能停止动画循环,还能彻底释放Three.js占用的GPU和内存资源,解决你遇到的问题。
内容的提问来源于stack exchange,提问作者user1378824
相关产品推荐
相关产品推荐

