Search code examples
javascripthtmloopcanvasscale

Scaling HTML5 Canvas Image


Building a web page on which I am trying to set an image as the background of the main canvas. The actual image is 1600x805 and I am trying to code the application so that it will scale the image either up or down, according to the dimensions of the user's screen. In Prime.js I have an object that sets the properties of the application's canvas element located in index.html. Here is the code for that object:

function Prime(w,h){
        if(!(function(){
                    return Modernizr.canvas;
                })){ alert('Error'); return false; };
        this.context = null;
        this.self = this;        
        this.globalCanvasMain.w = w;
        this.globalCanvasMain.h = h;
        this.globalCanvasMain.set(this.self);
        this.background.setBg();
    }

    Prime.prototype = {
        constructor: Prime,
        self: this,
        globalCanvasMain: {
            x: 0,
            y: 0,
            set: function(ref){
                ref.context = document.getElementById('mainCanvas').getContext('2d');
                $("#mainCanvas").parent().css('position', 'relative');
                $("#mainCanvas").css({left: this.x, top: this.y, position: 'absolute'});
                $("#mainCanvas").width(this.w).height(this.h); 
            }
        },
        background: {
            bg: null,
            setBg: function(){
                this.bg = new Image();
                this.bg.src = 'res/background.jpg';
            }
        },
        drawAll: function(){

            this.context.drawImage(this.background.bg, 0,0, this.background.bg.width,this.background.bg.height, 
                                    this.globalCanvasMain.x,this.globalCanvasMain.y, this.globalCanvasMain.w,this.globalCanvasMain.h);
        }
    };

The primary interface through which external objects like this one will interact with the elements in index.html is home.js. Here's what happens there:

$(document).ready(function(){
    var prime = new Prime(window.innerWidth,window.innerHeight);
    setInterval(prime.drawAll(), 25);
});

For some reason, my call to the context's drawImage function clips only the top left corner from the image and scales it up to the size of the user's screen. Why can I not see the rest of the image?


Solution

  • The problem is that the image has probably not finished loading by the time you call setInterval. If the image is not properly loaded and decoded then drawImage will abort its operation:

    If the image isn't yet fully decoded, then nothing is drawn

    You need to make sure the image has loaded before attempting to draw it. Do this using the image's onload handler. This operation is asynchronous so it means you also need to deal with either a callback (or a promise):

    In the background object you need to supply a callback for the image loading, for example:

    ...
    background: {
        bg: null,
        setBg: function(callback) {
            this.bg = new Image();
            this.bg.onload = callback;          // supply onload handler before src
            this.bg.src = 'res/background.jpg';
        }
    },
    ...
    

    Now when the background is set wait for the callback before continue to drawAll() (though, you never seem to set a background which means drawImage tries to draw null):

    $(document).ready(function(){
        var prime = new Prime(window.innerWidth, window.innerHeight);
    
        // supply a callback function reference:
        prime.background.setBg(callbackImageSet);
    
        // image has loaded, now you can draw the background:
        function callbackImageSet() {
          setInterval(function() {
            prime.drawAll();
          }, 25);
        };
    

    If you want to draw the complete image scaled to fit the canvas you can simplify the call, just supply the new size (and this.globalCanvasMain.x/y doesn't seem to be defined? ):

    drawAll: function(){
        this.context.drawImage(this.background.bg, 0,0, 
                                                       this.globalCanvasMain.w,
                                                       this.globalCanvasMain.h);
    }
    

    I would recommend you to use requestAnimationFrame to draw the image as this will sync with the monitor update.

    Also remember to provide callbacks for onerror/onabort on the image object.