Placing a canvas in a div (tried existing forum solutions!)

Hi there, I’d like to place a three.js scene in a div, so I can better position this div in the flow of the web page.

I’ve searched the forum and tried answers to similar questions, but I’m not quite sure how to properly integrate them into my code tbh!

Here’s the current code for scene setup (there’s a fragment shader in html script tags, too):

let container;
let camera, scene, renderer;
let uniforms;

let loader=new THREE.TextureLoader();
let texture, _500;
loader.setCrossOrigin("anonymous");
loader.load(
  'https://s3-us-west-2.amazonaws.com/s.cdpn.io/982762/noise.png',
  (tex) => {
    texture = tex;
    texture.wrapS = THREE.RepeatWrapping;
    texture.wrapT = THREE.RepeatWrapping;
    texture.minFilter = THREE.LinearFilter;
    
    loader.load(
      'https://s3-us-west-2.amazonaws.com/s.cdpn.io/982762/500.png',
      (tex) => {
        _500 = tex;
        
        init();
        animate();
      }
    );
  }
);

function init() {
  container = document.getElementById( 'container' );

  camera = new THREE.Camera();
  camera.position.z = 1;

  scene = new THREE.Scene();

  var geometry = new THREE.PlaneBufferGeometry( 2, 2 );

  uniforms = {
    u_time: { type: "f", value: 1.0 },
    u_resolution: { type: "v2", value: new THREE.Vector2() },
    u_pxaspect: { type: 'f', value: window.devicePixelRatio },
    u_noise: { type: "t", value: texture },
    u_text500: { type: "t", value: _500 },
    u_mouse: { type: "v2", value: new THREE.Vector2(-.1, -.1) }
  };

  var material = new THREE.ShaderMaterial( {
    uniforms: uniforms,
    vertexShader: document.getElementById( 'vertexShader' ).textContent,
    fragmentShader: document.getElementById( 'fragmentShader' ).textContent
  } );
  material.extensions.derivatives = true;

  var mesh = new THREE.Mesh( geometry, material );
  scene.add( mesh );

  renderer = new THREE.WebGLRenderer();
  renderer.setPixelRatio( window.devicePixelRatio );

  container.appendChild( renderer.domElement );

  onWindowResize();
  window.addEventListener( 'resize', onWindowResize, false );

  document.addEventListener('pointermove', (e)=> {
    let ratio = window.innerHeight / window.innerWidth;
    uniforms.u_mouse.value.x = (e.pageX - window.innerWidth / 2) / window.innerWidth / ratio;
    uniforms.u_mouse.value.y = (e.pageY - window.innerHeight / 2) / window.innerHeight * -1;
    
    e.preventDefault();
  });
}

function onWindowResize( event ) {
  renderer.setSize( window.innerWidth, window.innerHeight );
  uniforms.u_resolution.value.x = renderer.domElement.width;
  uniforms.u_resolution.value.y = renderer.domElement.height;
}

function animate(delta) {
  requestAnimationFrame( animate );
  render(delta);
}

let capturer = new CCapture( { 
  verbose: true, 
  framerate: 60,
  // motionBlurFrames: 4,
  quality: 90,
  format: 'webm',
  workersPath: 'js/'
 } );
let capturing = false;

isCapturing = function(val) {
  if(val === false && window.capturing === true) {
    capturer.stop();
    capturer.save();
  } else if(val === true && window.capturing === false) {
    capturer.start();
  }
  capturing = val;
}
toggleCapture = function() {
  isCapturing(!capturing);
}

window.addEventListener('keyup', function(e) { if(e.keyCode == 68) toggleCapture(); });

let then = 0;
function render(delta) {
  
  uniforms.u_time.value = delta * 0.0005;
  renderer.render( scene, camera );
  
  if(capturing) {
    capturer.capture( renderer.domElement );
  }
}```

Thanks in advance for your kind advice to a beginner!

Check out this section in the great online book, Discover three.js by @looeee : https://discoverthreejs.com/book/first-steps/first-scene/

Basically all you need to do is instead of using the window’s innerWidth and innerHeight in your app, use the clientWidth and clientHeight of your container. Doing this allows you to control the size and position of the scene using CSS.

Hi @oldsegotia, thanks loads for your reply!

I’ve had a good dig through the doc, and will take some time to digest and learn.

I think what complicates my case a little is the mouse-over effect of the shader… lines 73-76 in my example position this with height and width code… so it seems to me I need to decipher this section alongside the container clientWidth/height setup of the renderer.

Here’s my pen - think it’s just the shader I need to position so it moves up and down, with the light origin centred under the cursor, and with movement limited to mouseover in the container and not the div above.

Tried playing with the values in ‘pointermove’ section… tricky to get there via trial and error alone!

Sorry for the deletions, but try this pattern:

document.addEventListener('pointermove', (e)=> {
    e.preventDefault();

    uniforms.u_mouse.value.x = ( e.clientX / container.clientWidth ) * 2 - 1;
    uniforms.u_mouse.value.y = -( e.clientY / container.clientHeight ) * 2 + 1;
 });

Hmmm - still doesn’t want to place the canvas in the container so it flows relative to the page…

    function init() {
      const container = document.querySelector('#container');
    
      camera = new THREE.Camera();
      camera.position.z = 1;
    
      scene = new THREE.Scene();
    
      var geometry = new THREE.PlaneBufferGeometry(2, 2);
    
      uniforms = {
        u_time: { type: "f", value: 1.0 },
        u_resolution: { type: "v2", value: new THREE.Vector2() },
        u_pxaspect: { type: 'f', value: window.devicePixelRatio },
        u_noise: { type: "t", value: texture },
        u_text500: { type: "t", value: _500 },
        u_mouse: { type: "v2", value: new THREE.Vector2(-.1, -.1) } };
    
    
      var material = new THREE.ShaderMaterial({
        uniforms: uniforms,
        vertexShader: document.getElementById('vertexShader').textContent,
        fragmentShader: document.getElementById('fragmentShader').textContent });
    
      material.extensions.derivatives = true;
    
      var mesh = new THREE.Mesh(geometry, material);
      scene.add(mesh);
    
      renderer = new THREE.WebGLRenderer();
      renderer.setPixelRatio(window.devicePixelRatio);
    
 // NEW PLACEMENT CODE - CONTAINER.CLIENT-H/W 
  
  renderer.setSize(container.clientWidth, container.clientHeight);
  
 /////

      container.append(renderer.domElement);
    
      // onWindowResize();
      // window.addEventListener('resize', onWindowResize, false);
    
      document.addEventListener('pointermove', (e)=> {
    e.preventDefault();

    uniforms.u_mouse.value.x = ( e.clientX / container.clientWidth ) * 2 - 1;
    uniforms.u_mouse.value.y = -( e.clientY / container.clientHeight ) * 2 + 1;
 
        e.preventDefault();
      });
    }

is this the type of result you’re looking for?

2 Likes

Nice one @Lawrence3DPK ! That’s what I gathered he was looking for. I was just putting together a full example myself. I shall now file it away and go back under my rock :blush:

1 Like

Spot on. Thank you!

1 Like

Really appreciate your input - that’s a fine rock you have, I’m still collecting pebbles :upside_down_face:

2 Likes

@Lawrence3DPK @oldsegotia - as you folks have been so kind and knowledgeable, could I push my luck and ask about converting the solution to a format I can use in my project?

I’m using barba.js and inline scripts don’t seem to play well with it… so I’m looking at loading all the required code (fragment shader, vertex shader and rendering code) in one chunk of code I can call when the specific page holding this animation loads.

I’ve been through loads of stackoverflow answers and tried lots of variations including XMLHttpRequest(); but figure a module import would do the trick.

In this Codepen I set up the files on my hosting server (in the correct format I think! within .js files), added the import at the top and then used

    vertexShader: vertexShader,
fragmentShader: fragmentShader,

to call the shaders within the material section, but I’m getting ‘shader not compiled’ errors.

Any pointers?

Appreciate I’m pushing my luck as I say, so will start a new thread if needs be!

Cheers!

You can inline your shader code using backticks to create template literals directly in your material like so:

const uniforms = {
    uTime: { value: 0 },
};

const material = new THREE.ShaderMaterial( {
    uniforms,
    vertexShader: `
        varying vec2 vUv;

        void main() {

            vUv = uv;

            gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
        }
    `,
    fragmentShader: `
        varying vec2 vUv;

        void main() {
            gl_FragColor = vec4( vUv, 1.0, 1.0 );
        }
    `,
} );

This pattern should get you what you need. All your code in the one file.

Ah ok. Great.

The fragment shader has a loooong code thread, with loads of float values etc… can all that go within those backticks?

Tried it and it and I’m getting a load of undefined errors relating uniforms e.g. ‘uniforms.u_mouse’ etc.

Here’s a fiddle of it working with the shader code inlined: https://jsfiddle.net/ef4su9p5/

If you’re having anymore issues, I’d say it would be best to make a new post :slightly_smiling_face:

2 Likes

@oldsegotia Round two to you sir! Massively grateful.

Had a #container element in my build already, changed the naming here and it worked, bar an unknown error thrown up by the ‘scroll’ function, which I couldn’t put my finger on… the declaration is there.

Anyway, commented that scroll section out and it still works.

Many thanks again!

1 Like