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

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;
        }
    };
})();

关键修改点说明

  1. 更新帧请求ID:在animate函数内部,每次调用requestAnimationFrame时都把返回的ID赋值给id_an,这样cancelAnimationFrame就能拿到最新的帧ID,彻底终止动画循环。
  2. 资源销毁逻辑:
    • 调用geometry.dispose()和material.dispose()释放几何体和材质占用的GPU资源
    • 从场景中移除网格并置空,避免内存引用残留
    • 清空场景所有子对象,确保没有遗漏的资源
    • 调用renderer.dispose()销毁渲染器,这是释放WebGL上下文、降低GPU占用的核心步骤
    • 从DOM中移除渲染器的canvas元素,彻底清理页面残留
  3. 重复调用防护:在run_land开头先调用end_landscapes,防止重复创建资源导致的内存泄漏。

这样修改后,当你切换幻灯片调用end_landscapes()时,不仅能停止动画循环,还能彻底释放Three.js占用的GPU和内存资源,解决你遇到的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:22:16