canvaswebglviewport

Resize viewport/canvas according to browser window size


I want my webgl app viewport and canvas to react to browser window size changes (that includes F11 button). I.e. I want the canvas to be always 100% width and height and I want the viewport size and aspect ratio to be appropriate. The former I achieve with simple:

<canvas style="width: 100%; height:100%;">

although I'm not sure if that's the way to go. The latter I was trying to achieve with some information I found here: Webgl gl.viewport change but obviously I'm doing something wrong. I do realize:

canvas.onresize = resizeViewport;

is wrong as canvas's size doesn't change very often (it's always 100% width and height) but I don't know how I should do it.

Here's an online version of the app: http://nps.netarteria.pl/gallery.


Solution

  • I think this works:

    var canvas;
    var gl;
    
    function render() {
      gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
        
      // Draw a 1 pixel border around the edge using 
      // the scissor test since it's easier than setting up
      // a lot of stuff
      gl.clearColor(1, 0, 0, 1);  // red
      gl.disable(gl.SCISSOR_TEST);
      gl.clear(gl.COLOR_BUFFER_BIT);
        
      gl.enable(gl.SCISSOR_TEST);
      gl.scissor(1, 1, gl.canvas.width - 2, gl.canvas.height - 2);
      gl.clearColor(0, 0, 1, 1);  // blue
      gl.clear(gl.COLOR_BUFFER_BIT);
    };
    
    function resizeCanvas() {
      var width = canvas.clientWidth;
      var height = canvas.clientHeight;
      if (canvas.width != width ||
          canvas.height != height) {
        canvas.width = width;
        canvas.height = height;
              
        // in this case just render when the window is resized.
        render();
      }
    }
    
    function main() {
      canvas = document.getElementById("c");
      gl = canvas.getContext("webgl");
      resizeCanvas();
    }
    
    window.addEventListener('resize', resizeCanvas);
    main();
    html, body { 
      margin: 0;
      height: 100%;
    }
    #c {
      width: 100%;
      height: 100%;
      display: block;
    }
    <canvas id="c"></canvas>

    As far as I know, resize only works on the window. The HTML5 spec has unfortunately not added resize events to other elements. (update: ResizeObserver was added 2018)

    Note: If you are always rendering (like a game for example) then you don't need to listen for the resize event. Just call resizeCanvas at the beginning of your render loop. If the browser has resized the canvas, regardless of its container, the code will see the size no longer matches and update the size of the canvas's drawingbuffer.

    The proper viewport size for rendering the the canvas is almost always:

    // Set the viewport to be the size of the canvas's drawingBuffer.
    
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    

    Whereas if you are using a typical 3D math library, you also have a projection matrix and a function—usually called perspective which takes a fieidOfView, aspect, zNear and zFar parameters. The correct aspect for nearly all WebGL programs is:

    // Set the aspect of our perspective matrix to match the size
    // the canvas is displayed at.
    
    var aspect = gl.canvas.clientWidth / gl.canvas.clientHeight
    ???.perspective(fieldOfView, aspect, zNear, zFar);
    

    Update

    Apparently you need to cache the clientWidth and clientHeight. The reason is setting canvas.width will change the CSS layout which can change clientHeight

    Updated the code above.