0

Ok so I have just looked into the Canvas element but I have hit a snag.

I found a demo that moves three images over the top of one another using arrow keys.

However, I clipped one of them and the underlying image does not move now even though the above clipped image does.

HTML:

<canvas id="parallax-canvas">
    Sorry, your browser does not support HTML5 Canvas.
</canvas>

CSS:

.parallax-canvas {
    width: 400px;
    height: 300px;
}

JavaScript:

$(document).ready(function() {
    var w = $("#parallax-canvas").width();
    var h = $("#parallax-canvas").height();

    var sky = new Image();
    sky.src = "assets/img/sky.jpg";
    var skydx = 2;
    var skyx = 0;

    var mountains = new Image();
    mountains.src ="assets/img/mountains.png";
    var mountainsdx = 10;
    var mountainsx = 0;

    var jeep = new Image();
    jeep.src ="assets/img/jeep.png";
    var jeepx = 100; 
    var jeepy = 210; 
    var jeepsx = 0; 
    var jeepsxWidth = 155;

    var cntx =  $("#parallax-canvas")[0].getContext("2d");
    setInterval(draw, 10, cntx);

    $(window).keydown(function(evt) {
        switch (evt.keyCode) {
            case 37: // Left arrow
                if ((skyx + skydx) > skydx)
                  skyx -= skydx;
                else
                  skyx = w;

                if ((mountainsx + mountainsdx) > mountainsdx)
                  mountainsx -= mountainsdx;
                else
                  mountainsx = 398;

                if (jeepsx > 0) 
                    jeepsx -= jeepsxWidth;
                else 
                    jeepsx = (jeepsxWidth*2);

            break;

            case 39: // Right arrow
                if ((skyx + skydx) < (w - skydx))
                  skyx += skydx;
                else
                  skyx = 0;

                if ((mountainsx + mountainsdx) < (w - mountainsdx))
                  mountainsx += mountainsdx;
                else
                  mountainsx = 0;

                if (jeepsx < (jeepsxWidth*2))
                   jeepsx += jeepsxWidth;
                else
                   jeepsx = 0;

                break;
        }
    });

    function draw(_cntx) {
        drawRectangle(_cntx, 0, 0, w, h);
        _cntx.drawImage(sky, skyx, 0, 300, 300, 0, 0, w, 300);
        _cntx.beginPath();
        _cntx.moveTo(0,300);
        _cntx.lineTo(150,150);
        _cntx.lineTo(300, 300);
        _cntx.closePath();
        _cntx.clip();
        _cntx.drawImage(mountains, mountainsx, 0, 300, 300, 0, 0, w, 300);
        _cntx.drawImage(jeep, jeepsx, 0, jeepsxWidth, 60, jeepx, jeepy, 155, 60);
    }

    function drawRectangle(_cntx, x, y, w, h) {
        _cntx.beginPath();
        _cntx.rect(x,y,w,h);
        _cntx.closePath();
        _cntx.fill();
        _cntx.stroke();
    }
});

The part where I added the clipping is this section:

function draw(_cntx) {
    drawRectangle(_cntx, 0, 0, w, h);
    _cntx.drawImage(sky, skyx, 0, 300, 300, 0, 0, w, 300);
    _cntx.beginPath();
    _cntx.moveTo(0,300);
    _cntx.lineTo(150,150);
    _cntx.lineTo(300, 300);
    _cntx.closePath();
    _cntx.clip();
    _cntx.drawImage(mountains, mountainsx, 0, 300, 300, 0, 0, w, 300);
    _cntx.drawImage(jeep, jeepsx, 0, jeepsxWidth, 60, jeepx, jeepy, 155, 60);
}

If you remove from and including the beginPath method to the clip method it will allow the three images to all move.

WHAT IS HAPPENING: The sky image does not move when pressing left and right arrow keys, the mountain and jeep are clipped but do move within the clipped region.

WHAT I WANT TO HAPPEN: The sky image to move as well as the mountain and jeep in the clipped region.

ALSO:

I would like to know how to move the clipped region with the arrow presses. What I mean is, at the moment there is a clipped region where by pressing left and right the images move but the clipped (cut out) region remains still. I would like to know (if possible) how to go about moving the clipped region with the arrow keys.

Demo being used: http://www.ibm.com/developerworks/web/library/wa-parallaxprocessing/

Unfortunately cannot setup a Fiddle as the images are not on the site so cannot be given a URL but the .zip is on the site and by copying and pasting the bottom part of code where their method is in the JavaScript file that is what I am getting.

Thanks for any help!

EDIT: Thanks Ken for your help with shortening code and efficiency. Nothing so far though has answered my initial questions of how to move the images and also the clipping positions.

Rich
  • 103
  • 1
  • 8
  • JScript vs. JavaScript – Tom Chung Nov 30 '13 at 04:04
  • Nicely spotted Tom, forgive me I'm a bit tired at the moment, haha. – Rich Nov 30 '13 at 04:09
  • Could you setup a fiddle for us? –  Nov 30 '13 at 04:15
  • The demo I use (http://www.ibm.com/developerworks/web/library/wa-parallaxprocessing/) unfortunately doesn't actually have the images on the site so I cannot give a URL for them on Fiddle. But (I know this is asking a bit) but at the bottom of that link the .zip can be downloaded and by copying and pasting the "beginPath" method including and up to the "clip" method in the bottom section of code in my post you can see what I am getting. – Rich Nov 30 '13 at 04:26
  • You can upload the images to imgur.com but I provided an answer non-the-less. See if that helps you out so far. –  Nov 30 '13 at 04:27

1 Answers1

1

You need to set a size for your canvas - don't use CSS to set the size of a canvas:

<canvas id="parallax-canvas" width=500 height=300>
    Sorry, your browser does not support HTML5 Canvas.
</canvas>

Likewise you need to read the proper size from the canvas:

$(document).ready(function() {
    var canvas = $("#parallax-canvas")[0];
    var w = canvas.width;
    var h = canvas.height;
    ...

The for each time you hit the cursor keys you need to redraw everything. The canvas doesn't know what is drawn into it - it's just a bunch of pixels so we need to provide the logic for updates ourselves.

Update seem as I missed you're doing the redraw from a setInterval loop so this doesn not apply so much, but I let the example stay as it will probably be a better approach as you only need to update when something is actually changing. A loop that redraws everything all the time will quickly drain batteries for example..

For example:

$(window).keydown(function(evt) {
    switch (evt.keyCode) {
        case 37: // Left arrow
            if ((skyx + skydx) > skydx)
              skyx -= skydx;
            else
              skyx = w;

            if ((mountainsx + mountainsdx) > mountainsdx)
              mountainsx -= mountainsdx;
            else
              mountainsx = 398;

            if (jeepsx > 0) 
                jeepsx -= jeepsxWidth;
            else 
                jeepsx = (jeepsxWidth*2);

        draw(cntx );

        break;
        ...

This you need to repeat for the other moves as well.

You will also run into problems with the way you are loading the images as loading is asynchronous. You need to handle loading by tapping into the onload handler:

var sky = new Image();
sky.onload = functionToHandleLoad;
sky.src = "assets/img/sky.jpg";

As you are loading many images you would need to count the images or use a bulk loader such as my YAIL loader.

For clipping it's important to use save/restore as currently browsers doesn't handle manual reset of clip regions that well:

function draw(_cntx) {
    drawRectangle(_cntx, 0, 0, w, h);
    _cntx.drawImage(sky, skyx, 0, 300, 300, 0, 0, w, 300);
    _cntx.beginPath();
    _cntx.moveTo(0,300);
    _cntx.lineTo(150,150);
    _cntx.lineTo(300, 300);
    _cntx.closePath();

    _cntx.save();     /// save current clip region

    _cntx.clip();
    _cntx.drawImage(mountains, mountainsx, 0, 300, 300, 0, 0, w, 300);
    _cntx.drawImage(jeep, jeepsx, 0, jeepsxWidth, 60, jeepx, jeepy, 155, 60);

    _cntx.restore();  /// reset clip
}

At the time of answering there where no fiddle available so I haven't checked the other parts of the code, but this should be a good start I think.

This function

function drawRectangle(_cntx, x, y, w, h) {
    _cntx.beginPath();
    _cntx.rect(x,y,w,h);
    _cntx.closePath();
    _cntx.fill();
    _cntx.stroke();
}

can be simplified to:

function drawRectangle(_cntx, x, y, w, h) {
    _cntx.fillRect(x, y, w, h);
    _cntx.strokeRect(x, y, w, h);
}

No need to close path on a rect and beginPath is not needed with fillRect/strokeRect.

Community
  • 1
  • 1
  • Thanks for the reply Ken. Thanks for the link on setting the size of canvas'. The next part, "Likewise you need to read the proper size from the canvas" isn't that just adding an unnecessary extra line? Or am I missing something? When you say you need to redraw everything, the line I have before the keydown section "setInterval(draw, 10, cntx);" calls the draw method every 10 milliseconds passing the cntx variable to update the images. Does this not do what you are pointing out with the canvas needing to update regularly? ... – Rich Nov 30 '13 at 04:41
  • ...The many images issue, can't WaitForImages be used to account for the problem? And thanks for the last part about saving/restoring clips. – Rich Nov 30 '13 at 04:42
  • @user1928743 guess I missed that you used setInterval. When there isn't a fiddle I tend to read very quickly only. The w/h is a correction from your code - I don't know what you're are using it for but if you want to read the width/height of canvas you need to use the properties (not the css size) :) –  Nov 30 '13 at 04:44
  • @user1928743 Not sure what you mean with WaitForImages - there is no such method out-of-the-box. The proper way though is to handle onload as there can be many factors delaying/preventing an image from being loaded. A loader can make life easier also in regards to handle errors. –  Nov 30 '13 at 04:50
  • Looked into your method of updating and it is more efficient. It probably explains why my computer was running the page a bit sluggishly, especially as the demo put the updating every 10 milliseconds. Thanks for that. Looked into the loaders and understand why that is necessary, and also at the shortened code, thank you again. I don't suppose however you have any insight into why each image isn't moving though? And my other query on how to move the clipped region points on update? Thanks for your help, Ken. – Rich Nov 30 '13 at 05:01
  • Also, I noticed on the save/restore part, this would surely only work for one clipping? If I were to use another clip within that clip, how would it be possible to save both? – Rich Nov 30 '13 at 05:04
  • I just tried implementing the "draw" call in the keydown function but the page came up with nothing at all. When using the setInterval method it does work. Brains running a little low on power at the moment so it may be something basic that's the problem. Haha. – Rich Nov 30 '13 at 05:27