我试图创建一个3D编辑器,用户可以编辑一个3D场景,然后点击"play“按钮并查看结果。为了呈现结果,我使用了一个iframe
。以下是我的HTML
代码:
<iframe id="testing_frame" class="ui"></iframe>
类ui
就是position: absolute; top: 0;
。我不想有一个URL
或FILE
作为这个iframe
的src
,相反,我想直接写到它。我就是这样做的:
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
的内容,如下所示:
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.
这里我有一段视频展示了我的问题。(在我开始做任何事情之前,不要担心控制台中的事情)
谢谢!
编辑:这是开始/停止代码:
<button id='play_btn' onClick='test_iframe();';>Play</button>
这是按钮,下面是test_iframe()
函数:
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()
函数,它接收代码:
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
的新代码
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
的错误,但是中间的红色立方体没有出现。
发布于 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函数添加到页面移动操作中。
// 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 />
这比处理要复杂一些。以下是码页的工作代码和工作演示
<script src="your three.js url"></script>
<body>
<div id="root">
</div>
<button id='play_btn' onClick='test_iframe()'>Play</button>
</body>
// 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;
}
https://stackoverflow.com/questions/72872190
复制相似问题