Canvas Height stucked in 1080px

I’m trying to create a website using a Canvas as the background, similar to the Scroll Controls in React Three Fiber, but using just Three.js, HTML, and CSS.

My problem:
My canvas isn’t growing beyond 1080px in height. I noticed this issue when I tried to insert a plane at y: -5.

I believe I handled the resizing correctly:

import * as THREE from 'three';
import { models } from './models';
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
import { Videos } from './videos';

const sizes = {
  width: window.innerWidth,
  height: window.innerHeight,
};

const aspectRatio = sizes.width / sizes.height;

let renderer, scene, camera, fov, clock, velociraptor, canvas, environment, pmremGenerator, mixer, speed;
clock = new THREE.Clock();

experience();
environmentForModels();
animate();

export function experience() {
  scene = new THREE.Scene();
  canvas = document.getElementById('app');

  renderer = new THREE.WebGLRenderer({
    antialias: true,
    alpha: true
  });
  renderer.shadowMap.enabled = true;
  renderer.setSize(sizes.width, sizes.height);

  renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
  canvas.appendChild(renderer.domElement);

  camSettings();

  // Load models and create the mixer
  models(scene, (loadedModel, loadedMixer) => {
    velociraptor = loadedModel;
    mixer = loadedMixer;
  });

  Videos(scene);
  

  window.addEventListener('resize', updateRendererSizes);

  renderer.render(scene, camera);

  return scene;
}

function environmentForModels() {
  environment = new RoomEnvironment(renderer);
  pmremGenerator = new THREE.PMREMGenerator(renderer);
  scene.environment = pmremGenerator.fromScene(environment).texture;
}

function camSettings() {
  fov = window.innerWidth < 600 ? 10 : 25;
  camera = new THREE.PerspectiveCamera(fov, aspectRatio, 0.1, 1000);
  camera.position.z = 10;
  scene.add(camera);
}

function updateRendererSizes() {
  sizes.width = window.innerWidth;
  sizes.height = window.innerHeight;

  camera.aspect = sizes.width / sizes.height;
  camera.updateProjectionMatrix();

  renderer.setSize(sizes.width, sizes.height);
}

function animate() {
  requestAnimationFrame(animate);
  
  const delta = clock.getDelta();

  speed = .7;

  // Update the mixer with the adjusted speed
  if (mixer) {
    mixer.update(delta * speed);
  }

  renderer.render(scene, camera);
}

My HTML and CSS related to the canvas div:

 <div id="app" class="canvas"></div>

.canvas {
  position: absolute;
  width: 100%;
  height: 100%;

  z-index: 10; 
}

I created it this way because I want to control the text z-axis to insert some content in front of and behind the models. I have seen a lot of content on the forum, but I couldn’t find a solution to this issue.

Can you help me, please?
Thank you for your time! :slight_smile:

@iHast have you maybe tried a slightly different approach just to see if it makes any difference, like replacing your

const sizes = {
  width: window.innerWidth,
  height: window.innerHeight,
};

with variables, like

var ww = window.innerWidth;
var wh = window.innerHeight;

and adding your canvas in the renderer creation, like this:

  renderer = new THREE.WebGLRenderer({
    canvas: canvas,
    antialias: true,
    alpha: true
  });

Just a suggestion which might make no difference.

Window resize is only fired when the window itself changes.
If css is changing the layout of the page, the resize handler will not fire.

If you need to react to changes in the canvas itself, I think you have to watch/observe:
renderer.domElement.parentElement.clientWidth/Height and when they change, call your resize handler.

This can be fiddly, and sometimes changing the renderer size in response to the parent container size changing, can cause a cascade effect where:

threejs resizes the render, thereby resizing the canvas… (it modifies both canvas.width/height, and the canvas.style.width and canvas.style.height) the canvas resizing, then causes the page to re-layout which may change the parent size, and you end up in a resizing loop.

The renderer.resize(w,h, has a 3rd boolean parameter to make threejs not update the CSS which can prevent this, so for instance

renderer.resize(w,h,false)

Should decouple the canvas size from the CSS shenanigans.

1 Like

Do you suggest something to avoid this cascade effect, such as placing scenes or canvases in strategic sections? I was trying to avoid the idea of creating multiple canvases, scenes, or renderers. However, I want to create a single page with 3 assets positioned at different points along the Y-axis.

Thanks for your answer!!

Passing false to the renderer.resize(w,h,false) stops the cascade behavior.

The canvas has 2 different width/heights. The width/height of the dom element, (as set in the style) and the actual pixel width and height of the canvas.
By default threejs renderer sets Both of these to the w/h you pass to resize.
By passing false as the 3rd parameter, Only the pixel width/height is changed, and the visual width/height is left up to the dom.

Translating points from dom space to canvas space is a much gnarlier problem. You can use the CSSRenderer to facilitate these kinds of positional transformations… and it allows you to position dom elements as if they were objects in threejs.

https://threejs.org/examples/?q=css3d

https://threejs.org/docs/#examples/en/renderers/CSS3DRenderer

It’s a complicated topic, and would be exceedingly difficult to encapsulate in a post here.

If you want to synchronize dom elements with elements in 3d, (and you’re into React) r3f (react-three-fiber) might be your sweet spot since they do a lot of the heavy lifting in this regard.

3 Likes

Thank you so much, Manthrax!!
Someday I’m going to contribute in return!
I still have a lot to learn :sweat_smile:

1 Like

If it’s any consolation, I just spent a couple hours making a resize handler work correctly for a threejs canvas inside a div. :stuck_out_tongue: It’s not trivial.

1 Like

Thank you! :sweat_smile:

I’m studying every day to increase my knowledge of Three.js and GSAP. If possible, could you share your resize handler code? I would like to analyze it. :grin:

    let checkForResize = ()=>{
        let de = renderer.domElement.parentElement;
        let width = de.clientWidth;
        let height = de.clientHeight;
        if ((width != w) || (height != h)) {
            w = width;
            h = height;
            camera.aspect = w / h;
            camera.updateProjectionMatrix();
            renderer.setSize(w, h);
        }
    }

    const resizeObserver = new ResizeObserver(checkForResize);
    resizeObserver.observe(renderer.domElement.parentElement);

I’m using something like this ^ and then in css:


        .threeContainer{
        	position: absolute;
        	top: 0;
        	left: 0;
        	width: 100%;
        	height: 100%;
        	font-size: 16px;
        	display: flex;
            overflow:clip;
        }


        .glcanvas{            
            position: absolute;
            left:0px;
            top:0px;
        }

html:

    <div class = "threeContainer">
    <canvas id="glcanvas"></canvas>
    </div>
1 Like

@manthrax is the following essentially doing the same without a resizeObserver?

const sizes = {
  width: container.offsetWidth
  height: container.offsetHeight
}

const resize = () => {
  sizes.width = container.offsetWidth
  sizes.height = container.offsetHeight
  camera.aspect = sizes.width / sizes.height;
  camera.updateProjectionMatrix();
  renderer.setSize(sizes.width, sizes.height);
}
1 Like

For a full screen window it might be functionally equivalent if you’re calling resize on window resize.

If you’re calling resize inside the render loop, it would be recomputing projectionmatrix each frame… I don’t know if setSize is debounced internally by the browser, so it may not cause actual redundant resize, but I’m not sure, and might depend on the browser.

What I posted is designed to (hopefully) work if the renderer is inside a div that may resize independently of the window size changes… like if other components are added and trigger a layout of the page.

2 Likes

Thanks for the explanation, this makes sense and deffrentiates the two use cases well…