Scaling to a fixed point in KineticJS

2019-01-17 13:32发布

问题:

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();

回答1:

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);
});​


回答2:

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]
};


回答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);
});


回答4:

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;
}