Move HTML5 Canvas with a background image
Asked Answered
U

2

1

I want to visualize a huge diagram that is drawn in a HTML5 canvas. As depicted below, let’s imagine the world map, it’s impossible to visualize it all at the same time with a “decent” detail. Therefore, in my canvas I would like to be able to pan over it using the mouse to see the other countries that are not visible.

Does anyone know how to implement this sort of panning in a HTML5 canvas? Another feature would be the zoom in and out.

canvas diagram I've seen a few examples but I couldn't get them working nor they seam to address my question.

Thanks in advance!

Uneventful answered 5/8, 2013 at 12:11 Comment(2)
So you basically want to move (pan) an image inside the HTML5 canvas object, controlled by the mouse? Also zoom in/out on it.Faggot
Exactly! I would like to drag the image around.Uneventful
F
3

To achieve a panning functionality with a peep-hole it's simply a matter of two draw operations, one full and one clipped.

To get this result you can do the following (see full code here):

Setup variables:

var ctx = canvas.getContext('2d'),

    ix = 0, iy = 0,           /// image position
    offsetX = 0, offsetY = 0, /// current offsets
    deltaX, deltaY,           /// deltas from mouse down
    mouseDown = false,        /// in mouse drag
    img = null,               /// background
    rect,                     /// rect position
    rectW = 200, rectH = 150; /// size of highlight area

Set up the main functions that you use to set size according to window size (including on resize):

/// calc canvas w/h in relation to window as well as
/// setting rectangle in center with the pre-defined
/// width and height
function setSize() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    rect = [canvas.width * 0.5 - rectW * 0.5,
            canvas.height * 0.5 - rectH * 0.5,
            rectW, rectH]
    update();
}

/// window resize so recalc canvas and rect
window.onresize = setSize;

The main function in this is the draw function. Here we draw the image on the position calculated by mouse moving (see next section).

  • First step to get that washed-out look is to set alpha down to about 0.2 (you could also draw a transparent rectangle on top but this is more efficient).
  • Then draw the complete image.
  • Reset alpha
  • Draw the peep-hole using clipping with corrected offsets for the source.

-

/// main draw
function update() {
    if (img === null) return;

    /// limit x/y as drawImage cannot draw with negative
    /// offsets for clipping
    if (ix + offsetX > rect[0]) ix = rect[0] - offsetX;
    if (iy + offsetY > rect[1]) iy = rect[1] - offsetY;

    /// clear background to clear off garbage
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    /// make everything transparent
    ctx.globalAlpha = 0.2;

    /// draw complete background
    ctx.drawImage(img, ix + offsetX, iy + offsetY);

    /// reset alpha as we need opacity for next draw
    ctx.globalAlpha = 1;

    /// draw a clipped version of the background and
    /// adjust for offset and image position
    ctx.drawImage(img, -ix - offsetX + rect[0],  /// sx
                       -iy - offsetY + rect[1],  /// sy
                       rect[2], rect[3],         /// sw/h

                       /// destination
                       rect[0], rect[1], rect[2], rect[3]);

    /// make a nice sharp border by offsetting it half pixel
    ctx.strokeRect(rect[0] + 0.5, rect[1] + 0.5, rect[2], rect[3]);
}

Now it's a matter of handling mouse down, move and up and calculate the offsets -

In the mouse down we store current mouse positions that we'll use for calculating deltas on mouse move:

canvas.onmousedown = function(e) {

    /// don't do anything until we have an image
    if (img === null) return;

    /// correct mouse pos
    var coords = getPos(e),
        x = coords[0],
        y = coords[1];

    /// store current position to calc deltas
    deltaX = x;
    deltaY = y;

    /// here we go..
    mouseDown = true;
}

Here we use the deltas to avoid image jumping setting the corner to mouse position. The deltas are transferred as offsets to the update function:

canvas.onmousemove = function(e) {

    /// in a drag?
    if (mouseDown === true) {

        var coords = getPos(e),
            x = coords[0],
            y = coords[1];

        /// offset = current - original position
        offsetX = x - deltaX;
        offsetY = y - deltaY; 

        /// redraw what we have so far
        update();
    }
}

And finally on mouse up we make the offsets a permanent part of the image position:

document.onmouseup = function(e) {

    /// was in a drag?
    if (mouseDown === true) {
        /// not any more!!!
        mouseDown = false;

        /// make image pos. permanent
        ix += offsetX;
        iy += offsetY;

        /// so we need to reset offsets as well
        offsetX = offsetY = 0;
    }
}

For zooming the canvas I believe this is already answered in this post - you should be able to merge this with the answer given here:
Zoom Canvas to Mouse Cursor

Fibro answered 5/8, 2013 at 16:20 Comment(6)
You definitely posted the answer, many thanks! I have the padding working now, but regarding the zoom I already saw that post, but in that case whenever we want to zoom we have to redraw the whole background picture (translate, scale), right? I was trying to play with the drawImage function to be able to zoom by capturing more of the background image, this way avoiding the redraw of the background. Would this be possible?Uneventful
@Uneventful you mean like using the square as a magnifier? If so you can do something like this: jsfiddle.net/AbdiasSoftware/Us9nH/3Fibro
again many thanks for all your help! It is indeed like what you posted, but I want to be able to zoom in but also zoom out according to the scroll of the mouse. Btw have you noticed on the your first fiddle that when you circle around with the mouse there’s a sort of magnifying effect when you move towards the bottom part of the map? Thanks!Uneventful
Actually, I think this effect is due to reaching the end of the image. The highlight frame reaches the end of the image and adapts the width of the captured frame to the rectangle giving this stretched effect.Uneventful
@KenFyrstenberg : Sir, how can I have the movable function ONLY be available inside the canvas? And how can I have the overflow of the image with respect to the canvas be hidden?Radial
isn't it inefficient to drawImg each time - wouldn't using a translate be better?Bufflehead
K
1

To do something like you have requested, it is just a case of having 2 canvases, each with different z-index. one canvas smaller than the other and position set to the x and y of the mouse.

Then you just display on the small canvas the correct image based on the position of the x and y on the small canvas in relation to the larger canvas.

However your question is asking for a specific solution, which unless someone has done and they are willing to just dump their code, you're going to find it hard to get a complete answer. I hope it goes well though.

Kaycekaycee answered 5/8, 2013 at 12:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.