Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I use scrollbars to pan around a virtual canvas?

I want to create a jQuery plugin that implements a virtual HTML5 Canvas, i.e. a canvas that is physically no larger (or not much larger) that its appearance on the page. But the contents of what is intended to be shown on the canvas may be many times larger that the canvas and will be dynamically redrawn on depending on scrollbars.

You would think that this is very common functionality, but so far i have not been able to find examples either with jQuery plugins or otherwise. This is very similar to what e.g. SlickGrid does for a Div, except this is with a Canvas. I can think of two solutions:

  1. Use a jQuery UI Slider to implement a scrollbar as a completely separate element and use its event to control the Canvas redrawing.

  2. Do whatever it is SlickGrid does for the Div. It appears to make a Div that is slightly larger than what is being displayed and the hook up to scroll events to dynamically add/remove element to/from the Div. But I can't see how it modifies the scrollbar to make it appear as if there is much more in the Div that what is currently being displayed.

What would you recommend? Sample code would be greatly appreciated.

like image 693
jensk Avatar asked Nov 01 '11 08:11

jensk


People also ask

How do you implement scrollbar in canvas?

Specify the total width of the canvas then wrap it in a div. Set the div to overflow: scroll and give that the 500px width. You should then have scrollbars allowing you to scroll and see the hidden parts of the canvas. Repeat this for all of the canvases.


2 Answers

I dug into the SlickGrid code and used method 2) (sort of) - it was something like this I had in mind:

/*
    [email protected]

    Simple virtual CANVAS controlled by a native scrollbar made with two DIVs
    Uses jCanvas by Caleb Evans (http://calebevans.me/projects/jcanvas/index.php)
    Thanks to Michael Leibman of SlickGrid fame for ideas.

    Still need to clean it up (get rid of hardcoded values) and make it a nice, configurable
    jQuery component.

    Currently also redraws the entire canvas on each scroll event. Could be optimized to 
    do real browser scrolling and only redrawing the needed parts.

    Another gotcha is that since it is the zero width DIVs that causes the scroll events,
    mouse wheel, trackpad, touchscreen etc. scrolling over the Canvas will not work - only
    the scrollbar is active. To solve this, one could make the Canvas larger inside a 
    smaller DIV too, catch scroll events from it and perform redrawing and setting the DIV's
    scrollTop accordingly.    
*/

var h = 10000; // virtual canvas height
var vp = 400; // viewport height
var viewport, fakescrolldiv, canvas;

function onScroll() {
    var scrollTop = viewport.scrollTop();
    console.log("onScroll scrollTop=" + scrollTop);


    $("canvas").clearCanvas();

    // Red box top
    $("canvas").drawRect({
        fillStyle: "#F00",
        x: 150,
        y: 20 - scrollTop,
        width: 100,
        height: 100,
        fromCenter: false
    });

    // Green box middle
    $("canvas").drawRect({
        fillStyle: "#0F0",
        x: 150,
        y: 140 - scrollTop,
        width: 100,
        height: 100,
        fromCenter: false
    });

    // Blue box bottom
    $("canvas").drawRect({
        fillStyle: "#00F",
        x: 150,
        y: 260 - scrollTop,
        width: 100,
        height: 100,
        fromCenter: false
    });

    var i = 0;
    for (i = 0; i <= 396; i++) {
        $("canvas").drawLine({
            strokeStyle: "#000",
            strokeWidth: 1,
            x1: 0,
            y1: i,
            x2: (scrollTop + i) % 50,
            y2: i
        });
        if ((scrollTop + i) % 50 === 0) {
            $("canvas").drawText({
                fillStyle: "#729fcf",
                text: (scrollTop + i).toString(),
                align: "left",
                baseline: "top",
                font: "normal 12pt Verdana",
                x: 60,
                y: i
            });

        }
    }
}

$(function() {
    viewport = $("#viewport");
    fakescrolldiv = $("#fakescrolldiv");
    canvas = $("#gfx");

    viewport.css("height", vp);
    fakescrolldiv.css("height", h);

    viewport.scroll(onScroll);
    viewport.trigger("scroll");
});

Live demo

Any suggestions for improvements or simplifications are greatly appreciated.

like image 126
jensk Avatar answered Oct 17 '22 07:10

jensk


From my personal experience your option (1) is an attractive choice, but there might be some interesting points in making it into a useable jQuery plugin.

I've been working on a financial data visualisation system that explicitly uses HTML5 Canvas for graph and chart drawing. We have different virtual 'scenes' or 'slides' in the canvas which 'slide in' and 'slide out' in the canvas, much like the same way you'd navigate in a big virtual canvas. All the event handling buttons are exclusively drawn on the canvas which dictate which screen we'd be showing, but we have a one/two normal HTML forms that take user inputs and brings those 'slides'. We use jQuery to handle events from these text boxes, but the jQuery codes are deeply nested inside the other Canvas drawing codes, (unlike an externalised call, which would be an idea candidate for making a plugin).

Sliding or updating the canvas is another thing. This is because not only it depends on the jQuery event that triggers the update but it also depends on the Canvas Framework (plain code or KineticJS, EaselJS, jCotton etc) that is responsible for the update. If you use a framework, you'll need to interface with the framework as well.

For simplicity let's assume that there is a callback function that you can call for that Canvas framework with parameters like movement offset (x, y), and the framework will add/remove this offset to the x and y positions of all the objects drawn in the canvas, most Canvas drawing frameworks also have a render() function that it calls periodically so that next time it draws the scene the results will automatically show (in your case, scrolling through the virtual canvas).

So it basically comes down to not only writing it as a jQuery plugin but also a binding it to a particular Canvas Framework e.g. KineticJS or others.

If you use basic Canvas functions instead of using any of those Frameworks, then it's another story, you can write your own render and update functions for the canvas, but in that case it'll be restricting the potential user to adhere the limitations of your drawing functions, unless you expose an API to extend them; but then again, that means you are writing your own Canvas framework :)

I'm not sure if I understood your problem correctly, in that case you can safely ignore my advice :), but If I'm right, my opinion would be: making a plugin like this would require also binding to a Canvas framework to make it really useable.

Hope it helps.

like image 26
Abu Zaher Faridee Avatar answered Oct 17 '22 07:10

Abu Zaher Faridee