Hello I'm trying to add canvas with a sense using three.js I have
<div id="earth3D" bind:this={earth3DCanvas} />
and in order to add canvase in my js section, I do
let earth3DCanvas;
onMount(() => {
const child = document.createElement(renderer.domElement);
earth3DCanvas.appendChild(child);
});
but for some reason I'm getting this error and I have no idea what it means.
VM2002:1 Uncaught DOMException: Failed to execute 'createElement' on 'Document': The tag name provided ('[object HTMLCanvasElement]') is not a valid name.
at HTMLDocument.createElement (<anonymous>:1:1536)
at http://localhost:5000/build/bundle.js:51519:30
at run (http://localhost:5000/build/bundle.js:14:16)
at Array.map (<anonymous>)
at http://localhost:5000/build/bundle.js:430:49
at flush (http://localhost:5000/build/bundle.js:245:21)
at init (http://localhost:5000/build/bundle.js:520:13)
at new App (http://localhost:5000/build/bundle.js:51598:7)
at http://localhost:5000/build/bundle.js:51609:17
at http://localhost:5000/build/bundle.js:51618:3
Bind a container first then onMount add the renderer to this container
<script>
import { onMount } from 'svelte';
let container;
onMount(async () => {
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1;
renderer.outputEncoding = THREE.sRGBEncoding;
container.appendChild(renderer.domElement);
});
</script>
<div id="container" bind:this={container} />
Here is a working repl
https://svelte.dev/repl/446c6476fc0444b19ec95c0044564b96?version=3.44.2
here is the fix if you want to use a conditional statement
<script>
const initContainer = (node) => {
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1;
renderer.outputEncoding = THREE.sRGBEncoding;
node.appendChild(renderer.domElement);
}
</script>
{#if condition}
<div id="container" use:initContainer />
{/if}
here is a working repl using this approach
https://svelte.dev/repl/446c6476fc0444b19ec95c0044564b96?version=3.44.2
Document.createElement() expects a tag name as parameter (so a string like 'canvas' or 'div'), but you're passing it an HTMLCanvasElement object instead.
It's like you were attempting to recreate an already existing canvas.
I am not sure what your actual goal is, but if you want to create and append a brand new canvas, then you would do:
onMount(() => {
const child = document.createElement('canvas');
earth3DCanvas.appendChild(child);
});
If you instead want to append the canvas referred to by renderer.domElement to your container div:
onMount(() => {
earth3DCanvas.appendChild(renderer.domElement);
});
Note that if the canvas was already part of the DOM (which seems to be the case given the renderer.domElement name?), then it will be moved, not duplicated.
Related
I am practicing threejs while watching the tutorial.
However, one problem occurred.
Mouse control code is not working. Only black screen.
I checked, but it's exactly the same code as the tutorial.
But mine doesn't work.
I am the latest version of Chrome and I got the latest file from OrbitControls.js from GitHub.
I think the only difference from the tutorial is the OrbitControls.js file.
What should I do?
<html>
<head>
<meta charset="utf-8" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
<script src="js/controls/OrbitControls.js"></script>
<script>
window.addEventListener('load', init);
function init() {
const width = 960;
const height = 540;
const renderer = new THREE.WebGLRenderer({
canvas: document.querySelector('#myCanvas')
});
renderer.setSize(width, height);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, width / height);
camera.position.set(0, 0, 1000);
const controls = new THREE.OrbitControls(camera);
const mesh = new THREE.Mesh(
new THREE.BoxGeometry(300, 300, 300),
new THREE.MeshNormalMaterial()
);
scene.add(mesh);
tick();
function tick() {
renderer.render(scene, camera);
requestAnimationFrame(tick);
}
}
</script>
</head>
<body>
<canvas id="myCanvas"></canvas>
</body>
</html>
One common root cause for such importing issues is the usage of three.js components from different releases. You can easily verify this by importing your code like so:
<script src="https://cdn.jsdelivr.net/npm/three#0.110/build/three.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three#0.110/examples/js/controls/OrbitControls.js"></script>
Another possible problem is the way you create OrbitControls. In recent versions of three.js, the second parameter is mandatory. Meaning the code should look like so:
const controls = new THREE.OrbitControls(camera, renderer.domElement);
I started working with this example where both, a CSS2D Renderer as well as a WebGL Renderer is used - it works fine.
Now, I want to load object files and add CSS Labels to it. I started with the OBJ-Loader Example. There, the renderer is created as follows (all the following code snippets refer to the loader example):
initGL: function () {
this.renderer = new THREE.WebGLRenderer( {
canvas: this.canvas,
antialias: true,
autoClear: true
} ); .....
Thre renderer is called as follows:
render: function () {
if ( ! this.renderer.autoClear ) this.renderer.clear();
this.controls.update();
this.renderer.render( this.scene, this.camera );
}
My problem is that I do not know how to integrate the CSS2 Renderer. I simply tried to add the new labelrenderer to the same positions of the WebGLRenderer but it seems not to work:
initGL: function () {
this.renderer = new THREE.WebGLRenderer( {
canvas: this.canvas,
antialias: true,
autoClear: true
} ); //same code as before
this.labelRenderer = new THREE.CSS2DRenderer();
document.getElementById( 'example' ).appendChild(this.labelRenderer.domElement );....
I tried to update the render also at the same position of the WebGL Renderer:
render: function () {
if ( ! this.renderer.autoClear ) this.renderer.clear();
this.controls.update();
this.renderer.render( this.scene, this.camera );
//same code as before
this.labelRenderer.render(this.scene,this.camera);
}
The result: the WebGL renderer seem to work (the model is loaded) but the webgl renderer is not working... I do not get any error message but I simply do not see any label. What do I wrong? I create the label as follows:
var earthDiv = document.createElement( 'div' );
earthDiv.className = 'label';
earthDiv.textContent = 'MYLABEL IS NOT SHOWN';
earthDiv.style.marginTop = '-1em';
var earthLabel = new THREE.CSS2DObject( earthDiv );
earthLabel.position.set( 0, 3, 0 );
scope.scene.add(earthLabel );
I found the solution. The problem was that the CCS2 Object was added to the wrong div:
document.getElementById( 'example' ).appendChild(this.labelRenderer.domElement );
Of course, both, the canvas from the WebGL Renderer and the dom of the label Renderer have to be added to the same parent (the element example was the dom of the webgl renderer)
Thank you soo much for your support!
I use domElement to append the data in window. But how can I use html div tag to append it to the window?
I got the error
Cannot read property 'appendChild' of null
where I can't append my three js code in div element.
<html>
<head>
<title>
Ajay's Beginners guide
</title>
</head>
<body>
<script src="../build/three.js"></script>
<script>
var scene, camera, renderer;
function init() {
var scene = new THREE.Scene();
var WIDTH = window.innerWidth,
HEIGHT = window.innerHeight;
}
renderer = new THREE.WebGLRenderer();
// renderer.setSize(WIDTH, HEIGHT);
console.log(document.body);
canvas = document.getElementById('mycanvas');
canvas.appendChild(renderer.domElement);
renderer.setClearColorHex(0x333F47, 1);
//color and opacity
var light = new THREE.PointLight(0xffffff);
light.position.set(-100, 200, 100);
scene.add(light);
camera = new THREE.PerspectiveCamera(45, WIDTH / HEIGHT, 0.1, 2000);
camera.set.position(0, 6, 0);
scene.add(camera);
var loader = new THREE.JSONLoader();
loader.load("models/treehouse_logo.js", function (geometry) {
var material = new THREE.MeshLambertMaterial({ color: ox55B663 });
mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
});
controls = new THREE.OrbitControls(camera, render.domElement);
window.addEventListener('resize', function () {
var WIDTH = window.innerWidth;
var HEIGHT = window.innerHeight;
renderer.setSize(WIDTH, HEIGHT);
camera.aspect = WIDTH / HEIGHT;
camera.updateProjectionMatrix();
});
// init();
function animate() {
requestAnimationFrame();
renderer.render(scene, camera);
controls.update();
}
</script>
<div id="mycanvas">
</div>
</body>
</html>
Moving from comment to answer, thanks for the suggestion #Marquizzo.
Your script exists in the body before the div "canvas" element. The browser parses the page in order (mostly). So, you can move the script below the div so that it exists to be appended to as it is currently null when your code executes.
Here's a link to a related Stack Question on waiting for page contents before hooking them in code: Pure JavaScript equivalent of jQuery's $.ready() - how to call a function when the page/DOM is ready for it
When I load obj+mtl+jpg data with Three.js, the material of the object is set to MeshLambertMaterial according to three.js document. If I want to change the material to another one such as MeshBasicMaterial, how can I do that?
The following code loads obj+mtl+jpg from my server and display the data with MeshLambertMaterial. I tried to apply MeshBasicMaterial with the following code (commented), but it failed and the object with displayed in white.
<!DOCTYPE html>
<html lang="ja">
<head><meta charset="UTF-8"></head>
<script src="http://threejs.org/build/three.min.js"></script>
<script src="http://threejs.org/examples/js/loaders/MTLLoader.js"></script>
<script src="http://threejs.org/examples/js/loaders/OBJMTLLoader.js"></script>
<body>
<div id="canvas_frame"></div>
<script>
var canvasFrame, scene, renderer, camera;
canvasFrame = document.getElementById('canvas_frame');
renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
canvasFrame.appendChild( renderer.domElement );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 );
camera.position.set(50,50,50);
camera.lookAt( {x: 0, y: 0, z: 0} );
var ambient = new THREE.AmbientLight(0xFFFFFF);
scene.add(ambient);
function animate() {
renderer.render( scene, camera );
requestAnimationFrame( animate );
}
function loadObjMtl(objUrl, mtlUrl, url, x, y, z){
var loader = new THREE.OBJMTLLoader();
loader.crossOrigin = 'anonymous';
loader.load( objUrl, mtlUrl,
function ( object ) {
object.url = url;
object.position.set(x,y,z);
object.traverse( function( node ) {
if( node.material ) {
////This doesn't work. How can I apply material for loaded obj?
//var material = new THREE.MeshBasicMaterial();
//if ( node instanceof THREE.Mesh ) {
// node.material = material;
//}
}
});
scene.add ( object );
});
}
objUrl = "http://test2.psychic-vr-lab.com/temp/mesh_reduced.obj";
mtlUrl = "http://test2.psychic-vr-lab.com/temp/mesh_reduced.mtl";
loadObjMtl(objUrl,mtlUrl,"",0,0,0);
animate();
</script>
</body>
</html>
You see it totally white because you are not specifying any color for MeshBasicMaterial.
Try this:
var material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
You'll see all red: it is working.
I think that you probably want to distinguish the different meshes, or apply any texture, do you?
I know this is an old question but I was looking to do something similar and did it this way:
As of today Three.js r143 , the MTLLoader loads the materials as MeshPhongMaterial as seen in
MTLLoader.js 495: this.materials[ materialName ] = new MeshPhongMaterial( params );
Because I didn't want any reflections from my lighting. So I set the materials to
MeshLambertMaterial (see the docs) by just changing that line to:
MTLLoader.js 495: this.materials[ materialName ] = new MeshLambertMaterial( params );
And that's it!.
I know this approach will change the way every other model is loaded but in my case that's how I wanted it.
Hope this helps
I am trying to customize a shape by a slider, and then the shape is extruded by ExtrudeGeometry. First, I created 4 points as in "pts", and then created a "shape" with them, and finally, created a geomtery by extrude it through a line. However, I cannot make it works. My HTML code is as below:
<head>
<script src="three.min.js"> </script>
<script src="three.js"> </script>
<script src="TrackballControls.js"></script>
<script src="main.js"> </script>
</head>
<body onload="start()">
<div id="s"> </div>
</body>
</html>
and the "main" js file is as follows:
var camera, scene, renderer, phone, material, mesh;
var shape, extrudeSettings, randomSpline, controls;
var pts = [];
var randomPoints = [];
var v;
function start() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(75,window.innerWidth/window.innerHeight,1,10000);
camera.position.set( 0, 0, 500 );
renderer = new THREE.WebGLRenderer();
renderer.setClearColor( 0x222222 );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
controls = new THREE.TrackballControls( camera, renderer.domElement );
controls.minDistance = 100;
controls.maxDistance = 600;
scene.add(camera);
pts.push( new THREE.Vector3 (100,1,0));
pts.push( new THREE.Vector3 (-100,1,0));
pts.push( new THREE.Vector3 (-100,-1,0));
pts.push( new THREE.Vector3 (100,-1,0));
randomPoints.push( new THREE.Vector3 (0,0,10));
randomPoints.push( new THREE.Vector3 (0,0,-10));
randomSpline = new THREE.SplineCurve3( randomPoints );
extrudeSettings = {
steps : 200,
bevelEnabled : false,
extrudePath : randomSpline
};
shape = new THREE.Shape(pts);
phone = new THREE.ExtrudeGeometry(shape, extrudeSettings);
material = new THREE.MeshBasicMaterial({color:0x0000FF, wireframe:true});
mesh = new THREE.Mesh(phone,material);
scene.add(mesh);
var slider = document.createElement("input");
slider.setAttribute("type", "range");
slider.setAttribute("value", 90);
slider.setAttribute("id", "sliding");
document.getElementById("s").appendChild(slider);
document.getElementById("s").style.position = "absolute";
document.getElementById("s").style.zIndex="1";
animate();
}
function animate() {
requestAnimationFrame(animate);
v = document.getElementById("sliding").value;
pts[0].x = v;
pts[1].x = -v;
pts[2].x = -v;
pts[3].x = v;
shape.needsUpdate = true;
phone.verticesNeedUpdate = true;
phone.dynamic=true;
controls.update();
renderer.render( scene, camera );
}
When you create an ExtrudeGeometry, it creates the geometry based on the parameter, but it doesn't store the object you created it from. You'll need to create a new ExtrudeGeometry each time you want to update it, or you may be able to simply adjust phone.scale, depending on your needs.