首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >Three.js“上下文丢失”和“导入多个实例”?

Three.js“上下文丢失”和“导入多个实例”?
EN

Stack Overflow用户
提问于 2022-07-05 15:44:06
回答 1查看 166关注 0票数 0

我试图创建一个3D编辑器,用户可以编辑一个3D场景,然后点击"play“按钮并查看结果。为了呈现结果,我使用了一个iframe。以下是我的HTML代码:

代码语言:javascript
运行
复制
<iframe id="testing_frame" class="ui"></iframe>

ui就是position: absolute; top: 0;。我不想有一个URLFILE作为这个iframesrc,相反,我想直接写到它。我就是这样做的:

代码语言:javascript
运行
复制
generatedCode += `
    <!DOCTYPE html>
    <html>

    <head>
        <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"><\/script>
    </head>

    <body style="margin: 0;">
        <script async>"use strict"
            function init(){
                var scene = new THREE.Scene();
                var camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.1, 1000);
                var mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({
                    color: 0xff0000
                }));
                scene.add(mesh);
                camera.position.z = -5;
                camera.lookAt(0, 0, 0);
                var renderer = new THREE.WebGLRenderer();
                renderer.setSize(window.innerWidth, window.innerHeight);
                document.body.appendChild(renderer.domElement);

                function animate() {
                    requestAnimationFrame(animate);
                    renderer.render(scene, camera);
                }
                animate();
            }

            window.onload = init;
        <\/script>
    </body>

    </html>`;

该代码存储在变量generatedCode中,这就是我将写入iframe的内容,如下所示:

代码语言:javascript
运行
复制
var iframe = document.getElementById("testing_frame");
var iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
    
iframeDocument.open();
iframeDocument.write(generatedCode);
iframeDocument.close();

这个很好用。

我的问题是:我有一个start/stop 按钮,它在每次点击代码时都会运行。每次我按下stop,它写的是WARNING: Multiple instances of Three.js being imported.__,,如果我开始和停止测试大约10次,它写的是 THREE.WebGLRenderer: Context Lost.

这里我有一段视频展示了我的问题。(在我开始做任何事情之前,不要担心控制台中的事情)

谢谢!

编辑:这是开始/停止代码:

代码语言:javascript
运行
复制
<button id='play_btn' onClick='test_iframe();';>Play</button>

这是按钮,下面是test_iframe()函数:

代码语言:javascript
运行
复制
function test_iframe() {
    let code = generateCodeFromProjectData();
    var iframe = document.getElementById("testing_frame");
    var iframeDocument = iframe.contentDocument || iframe.contentWindow.document;
    
    //Write to iframe
    iframeDocument.open();
    iframeDocument.write(code);
    iframeDocument.close();

    //If they press play, change it to stop and show the iframe.
    if(document.getElementById("play_btn").innerHTML == "Play"){
        document.getElementById("testing_frame").style.display = "block";
        document.getElementById("play_btn").innerHTML = "Stop";
    } else { //If they press stop, hide the iframe and change it to play.
        document.getElementById("testing_frame").style.display = "none";
        document.getElementById("play_btn").innerHTML = "Play";
    }
}

最后,这里是generateCodeFromProjectData()函数,它接收代码:

代码语言:javascript
运行
复制
function generateCodeFromProjectData(){
    generatedCode = "";
    //Opening
    generatedCode += `
    <!DOCTYPE html>
    <html>

    <head>
        <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"><\/script>
    </head>

    <body style="margin: 0;">
        <script async>"use strict"
            function init(){
                var scene = new THREE.Scene();
                var camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.1, 1000);
                var mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({
                    color: 0xff0000
                }));
                scene.add(mesh);
                camera.position.z = -5;
                camera.lookAt(0, 0, 0);
                var renderer = new THREE.WebGLRenderer();
                renderer.setSize(window.innerWidth, window.innerHeight);
                document.body.appendChild(renderer.domElement);

                function animate() {
                    requestAnimationFrame(animate);
                    renderer.render(scene, camera);
                }
                animate();
            }

            window.onload = init;
        <\/script>
    </body>

    </html>`;

    return generatedCode;
}

编辑2:这是我的iframe的新代码

代码语言:javascript
运行
复制
generatedCode += `
    <!DOCTYPE html>
    <html>

    <head>
        <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r134/three.min.js"><\/script>
    </head>

    <body style="margin: 0;">
        <script>
            function init(){
                var scene = new THREE.Scene();
                var camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.1, 1000);
                var mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({
                    color: 0xff0000
                }));
                scene.add(mesh);
                camera.position.z = -5;
                camera.lookAt(0, 0, 0);
                var renderer = new THREE.WebGLRenderer();
                renderer.setSize(window.innerWidth, window.innerHeight);
                document.body.appendChild(renderer.domElement);

                function destroy() {
                    scene = null;
                    camera = null;
                    mesh = null;
                    renderer.dispose()
                }

                return {destroy};

                function animate() {
                    requestAnimationFrame(animate);
                    renderer.render(scene, camera);
                }
                animate();
            }
            window.onload = () => {
                var instance = init();
                window.onbeforeunload = () => {
                  instance.destroy();
                }
              }
        <\/script>
    </body>

    </html>`;

现在我没有得到Context Lost的错误,但是中间的红色立方体没有出现。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-07-06 16:17:41

在我看来,这就像是一个内存泄漏。除非手动释放或关闭选项卡(窗口),否则<iframe />中发生的任何事情都将留在内存中。在使用不同前端库的故事书时,我遇到了完全相同的问题;故事书也使用<iframe />作为单独的演示,但它总是需要手动取消分配。

手动内存释放,或者说dispose()函数在Three.JS中也是必要的。这是官方Three.js文档的摘录

为什么three.js不能自动处理对象? 这个问题被社区问了很多次,所以澄清这个问题是很重要的。事实是,three.js不知道用户创建的实体(如几何图形或材料)的生命周期或范围。这是应用程序的责任。例如,,即使一个材料目前没有用于渲染,它可能对下一个帧是必要的。因此,如果应用程序决定可以删除某个对象,则必须通过调用相应的that ()方法通知引擎。

它说,处置对Three.Object3D是必要的,但对WebGLRender只字未提。有趣的是,dispose()函数被列为WebGLRenderer的规范。

https://threejs.org/docs/?q=renderer#api/en/renderers/WebGLRenderer

若要将dispose操作同步到iframes,请使用window.onbeforeunload事件处理程序或将dispose函数添加到页面移动操作中。

代码语言:javascript
运行
复制
// add this code to your iframe code.
function init () {
  var scene = // ...
  var camera = // ...
  var mesh = // ...
  var renderer = // ...
  /* ... */
  function destroy() {
    scene = null;
    camera = null;
    mesh = null;
    renderer.dispose()
  }
  return {destroy};
}
window.onload = () => {
  var instance = init();
  window.onbeforeunload = () => {
    instance.destroy();
  }
}

编辑:答案只解决了context lost问题。它在销毁实例方面没有问题。Multiple instances being imported应被视为另一个问题。在<iframe/>中添加的任何内容都被视为与其父级相同的作用域。在iframe中一次又一次添加<script src="..." />会导致多个实例被导入。因此,要解决这个问题,必须从其父级提供代码。

  • generatedCode不应包括<script />
  • 代码应该在与iframe控制器代码相同的范围内提供,这样它就可以同步iframe和three.js呈现器的生命周期。

这比处理要复杂一些。以下是码页的工作代码和工作演示

代码语言:javascript
运行
复制
<script src="your three.js url"></script>
<body>
<div id="root">
</div>

<button id='play_btn' onClick='test_iframe()'>Play</button>
</body>
代码语言:javascript
运行
复制
// multiple instances being imported & context lost issue solved
function init(width, height) {
  console.log("init", width, height);
  var scene = new THREE.Scene();
  var camera = new THREE.PerspectiveCamera(90, width / height, 0.1, 1000);
  var mesh = new THREE.Mesh(
    new THREE.BoxGeometry(1, 1, 1),
    new THREE.MeshBasicMaterial({
      color: 0xff0000
    })
  );
  scene.add(mesh);
  camera.position.z = 5;
  camera.lookAt(0, 0, 0);
  var renderer = new THREE.WebGLRenderer();
  renderer.setSize(width, height);

  function destroy() {
    console.log("destroy");
    scene = null;
    camera = null;
    mesh = null;
    renderer.dispose();
  }

  function animate() {
    if (!renderer) return;
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
  }
  animate();
  return { destroy, domElement: renderer.domElement };
}

let iframe;
const getIframe = () => {
  const code = generateCodeFromProjectData();
  const root = document.getElementById("root");
  const iframe = document.createElement("iframe");
  iframe.setAttribute("id", "testing_frame");
  iframe.setAttribute("class", "ui");
  iframe.style.width = "300px";
  iframe.style.height = "300px";
  root.appendChild(iframe);
  const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
  iframeDoc.open();
  iframeDoc.write(code);
  iframeDoc.close();
  const body = iframeDoc.querySelector("body");
  const instance = init(body.clientWidth, body.clientHeight);
  body.appendChild(instance.domElement);
  function destroy() {
    root.removeChild(iframe);
    instance.destroy();
  }
  return { iframe, iframeDoc, destroy };
};

function test_iframe() {
  if (!iframe) {
    // init
    console.log("init");
    iframe = getIframe();
    document.getElementById("play_btn").innerHTML = "Stop";
    return;
  }
  console.log("dispose");
  // disposing
  document.getElementById("play_btn").innerHTML = "Play";
  iframe.destroy();
  iframe = null;
}

function generateCodeFromProjectData() {
  generatedCode = "";
  //Opening
  generatedCode += `
    <!DOCTYPE html>
    <html>
    <body style="margin: 0;width: 300px;height:300px;">
    </body>
    </html>`;

  return generatedCode;
}
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72872190

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档