Scaling to a fixed point in KineticJS
Asked Answered
K

4

8

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();
Kreit answered 11/9, 2012 at 14:50 Comment(0)
N
16

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);
});​
Nisen answered 1/12, 2012 at 19:59 Comment(4)
Hey thanks, this works fine but I have many layers so I can't set layers as a "draggable" . therefore I set stage draggable. When I move the stage as dragging , I can't zoom to desired zoom point. I have to recalculate somethings as counting stage x and y but I couldn't achive. Could u help me ?Thiele
@user1645941 Please share some working example of the problem you are dealing with (maybe as a fiddle) and I'll do what I can to help.Nisen
@Nisen but it's not working in firefox. Can you fix it?Remittance
@trungkien Wasn't working quite right in Chrome either, so I udpated KineticJS version to 4.4.3 and everything seems to be working now.Nisen
P
3

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]
};
Preposition answered 12/9, 2012 at 1:1 Comment(0)
L
3

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);
});
Locomotion answered 7/5, 2013 at 20:3 Comment(0)
C
2

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;
}
Corydalis answered 13/9, 2013 at 21:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.