Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scaling to a fixed point in KineticJS

I'm having some problems with scaling a container to a fixed point.
In my case I'm trying to scale (zoom) a stage to the mouse cursor.

Here is a way to do with pure canvas: http://phrogz.net/tmp/canvas_zoom_to_cursor.html (as discussed at Zoom Canvas to Mouse Cursor)

I just can't get figure out how to apply the same logic while using the KineticJS API.

Sample code:

var position = this.stage.getUserPosition();
var scale = Math.max(this.stage.getScale().x + (0.05 * (scaleUp ? 1 : -1)), 0);
this.stage.setScale(scale);
// Adjust scale to position...?
this.stage.draw();
like image 951
Skarbo Avatar asked Sep 11 '12 14:09

Skarbo


4 Answers

After a lot of struggling and searching and trying, using the tip provided by @Eric Rowell and the code posted in the SO question Zoom in on a point (using scale and translate) I finally got the zooming in and out of a fixed point working using KineticJS.

Here's a working DEMO.

And here's the code:

var ui = {
    stage: null,
    scale: 1,
    zoomFactor: 1.1,
    origin: {
        x: 0,
        y: 0
    },
    zoom: function(event) {
        event.preventDefault();
        var evt = event.originalEvent,
            mx = evt.clientX /* - canvas.offsetLeft */,
            my = evt.clientY /* - canvas.offsetTop */,
            wheel = evt.wheelDelta / 120;
        var zoom = (ui.zoomFactor - (evt.wheelDelta < 0 ? 0.2 : 0));
        var newscale = ui.scale * zoom;
        ui.origin.x = mx / ui.scale + ui.origin.x - mx / newscale;
        ui.origin.y = my / ui.scale + ui.origin.y - my / newscale;

        ui.stage.setOffset(ui.origin.x, ui.origin.y);
        ui.stage.setScale(newscale);
        ui.stage.draw();

        ui.scale *= zoom;
    }
};

$(function() {
    var width = $(document).width() - 2,
        height = $(document).height() - 5;
    var stage = ui.stage = new Kinetic.Stage({
        container: 'container',
        width: width,
        height: height
    });
    var layer = new Kinetic.Layer({
        draggable: true
    });
    var rectX = stage.getWidth() / 2 - 50;
    var rectY = stage.getHeight() / 2 - 25;

    var box = new Kinetic.Circle({
        x: 100,
        y: 100,
        radius: 50,
        fill: '#00D200',
        stroke: 'black',
        strokeWidth: 2,
    });

    // add cursor styling
    box.on('mouseover', function() {
        document.body.style.cursor = 'pointer';
    });
    box.on('mouseout', function() {
        document.body.style.cursor = 'default';
    });

    layer.add(box);
    stage.add(layer);

    $(stage.content).on('mousewheel', ui.zoom);
});​
like image 115
juan.facorro Avatar answered Nov 13 '22 07:11

juan.facorro


You need to offset the stage such that it's center point is positioned at the fixed point. Here's an example, because the center point of the stage is defaulted to the upper left corner of the canvas. Let's say that your stage is 600px wide and 400px tall, and you want the stage to zoom from the center. You would need to do this:

var stage = new Kinetic.Stage({
   container: 'container',
   width: 600,
   height: 400,
   offset: [300, 200]
};
like image 3
Eric Rowell Avatar answered Nov 13 '22 07:11

Eric Rowell


updated @juan.facorro's demo to scale shape instead of stage

jsFiddle

var ui = {
    stage: null,
    box: null,
    scale: 1,
    zoomFactor: 1.1,
    zoom: function(event) {
        event.preventDefault();
        var evt = event.originalEvent,
            mx = evt.offsetX,
            my = evt.offsetY,
            wheel = evt.wheelDelta / 120; //n or -n
        var zoom = (ui.zoomFactor - (evt.wheelDelta < 0 ? 0.2 : 0));
        var newscale = ui.scale * zoom;

        var origin = ui.box.getPosition();
        origin.x = mx - (mx - origin.x) * zoom;
        origin.y = my - (my - origin.y) * zoom;

        ui.box.setPosition(origin.x, origin.y);
        ui.box.setScale(newscale);
        ui.stage.draw();

        ui.scale *= zoom;
    }
};

$(function() {
    var width = $(document).width() - 2,
        height = $(document).height() - 5;
    var stage = ui.stage = new Kinetic.Stage({
        container: 'container',
        width: width,
        height: height
    });
    var layer = new Kinetic.Layer();
    var rectX = stage.getWidth() / 2 - 50;
    var rectY = stage.getHeight() / 2 - 25;

    var box = ui.box = new Kinetic.Circle({
        x: 100,
        y: 100,
        radius: 50,
        fill: '#00D200',
        stroke: 'black',
        strokeWidth: 2,
        draggable: true
    });

    // add cursor styling
    box.on('mouseover', function() {
        document.body.style.cursor = 'pointer';
    });
    box.on('mouseout', function() {
        document.body.style.cursor = 'default';
    });

    layer.add(box);
    stage.add(layer);

    $(stage.content).on('mousewheel', ui.zoom);
});
like image 3
Roman Avatar answered Nov 13 '22 06:11

Roman


The demo above only works if the x and y coordinates of the stage are 0. If e.g. the stage is draggable it will change these coordinates while dragging so they need to be included in the offset calculation. This can be achieved by subtracting them from the canvas offsets:

jsfiddle

zoom: function(event) {
    event.preventDefault();
    var evt = event.originalEvent,
        mx = evt.offsetX - ui.scale.getX(),
        my = evt.offsetY - ui.scale.getY(),
    var zoom = (ui.zoomFactor - (evt.wheelDelta < 0 ? 0.2 : 0));
    var newscale = ui.scale * zoom;

    var origin = ui.box.getPosition();
    origin.x = mx - (mx - origin.x) * zoom;
    origin.y = my - (my - origin.y) * zoom;

    ui.box.setPosition(origin.x, origin.y);
    ui.box.setScale(newscale);
    ui.stage.draw();

    ui.scale *= zoom;
}
like image 2
el_moke Avatar answered Nov 13 '22 06:11

el_moke