Zoom to point and scale (kineticjs+mousewheel)

This is a simple tip of how to create a zoom to point effect with kineticjs and mousewheel event.

So lets say that you want to zoom into a specific point on a canvas, of course you have used the excellent kineticjs framework, well then you are in luck.

To begin with this is the end-result for all the “quick-fix” people out there.

See the Pen Kineticjs zoom to point by Michael Dobekidis (@netgfx) on CodePen

The rest… come along…

Now first of all we declare the HTML that will be our stage:

<section id="graph">
  <div id="container"></div>
</section>

It is important to add the container in an external parent because we will want to reference that.

Now we add a tiny bit of css to keep items in place

#graph{
  display:block;
  width:100%;
  height:100%;
  float:left;
  min-width:1024px;
  min-height:1024px;
}

#container{
  display:inline-block;
  width:1200px;
  height:1024px;
  position:relative;
}

This is important because we want the #graph element to have clear dimentions.

Now lets create our canvas stage and some shapes

var scale = 1;
var stage;
var layer;
var min_scale = 0.1;
$(document).ready(function(){
 
   stage = new Kinetic.Stage({
        container: 'container',
        width: 1200,
        height: 1024,
     draggable:true
      });

      layer = new Kinetic.Layer();

      var rect = new Kinetic.Rect({
        x: 239,
        y: 75,
        width: 100,
        height: 50,
        fill: 'green',
        stroke: 'black',
        strokeWidth: 4
      });
 
    var circle = new Kinetic.Circle({
        x: 300,
        y: 260,
        radius: 70,
        fill: 'red',
        stroke: 'black',
        strokeWidth: 4
      });
   
  var hexagon = new Kinetic.RegularPolygon({
        x: stage.getWidth() / 2,
        y: 200,
        sides: 6,
        radius: 70,
        fill: 'aqua',
        stroke: 'black',
        strokeWidth: 4
      });

      // add the shape to the layer
      layer.add(hexagon);
      // add the shape to the layer
      layer.add(circle);

      // add the shape to the layer
      layer.add(rect);

      // add the layer to the stage
      stage.add(layer);

Now we have ourselves a nice stage and some shapes in it, all we need to do now is listen for the mousewheel event and add some code when that fires.

so at the bottom of the $(document).ready callback we add:

// add event listener //
  $('#graph').bind('mousewheel', onMouseWheel);

And the onMouseWheel function looks like this:

function onMouseWheel(e, delta) {
  delta = e.originalEvent.wheelDelta;
        //prevent only the actual wheel movement
        if (delta !== 0) {
            e.preventDefault();
        }

        var cur_scale;
        if (delta > 0) {
            cur_scale = scale + Math.abs(delta / 640);
        } else {
            cur_scale = scale - Math.abs(delta / 640);
        }

        //check for minimum scale limit
        if (cur_scale > min_scale) {
           
            var d=document.getElementById('graph');
                    var cnvsPos=getPos(d);

            var Apos = stage.getAbsolutePosition();
           
            var mousePos = stage.getMousePosition();

            var smallCalc  = (e.pageX - Apos.x - cnvsPos.x)/scale;
            var smallCalcY = (e.pageY - Apos.y - cnvsPos.y)/scale;

            var endCalc = (e.pageX - cnvsPos.x) - cur_scale*smallCalc;
            var endCalcY = (e.pageY - cnvsPos.y) - cur_scale*smallCalcY;

            scale = cur_scale;

            stage.setPosition( endCalc, endCalcY);

             layer.setScale(cur_scale);
                 layer.draw();
        }

    }

    function getPos(el){
        for (var lx=0, ly=0;
         el != null;
         lx += el.offsetLeft, ly += el.offsetTop, el = el.offsetParent);
        return {x: lx,y: ly};
    }

So what this function does is to get the “delta” property from the mousewheel event (that is how many lines we have “scrolled” with the mousewheel) and divide it with an offset to determine how much scaling we want per line scrolled.

Then we calculate the amount of panning that we need to do in order to “zoom to point”.
So we take the mouse position and subtract the canvas offset position from it, and divide by our current scale factor.

We then take this result and multiply it by our new scale factor to determine our exact “future” point.

It might be a bit confusing but it boils down to this: We move the stage so that it looks that we are centering wherever we have mousewheelded.

I hope you find this useful. Drop me a comment if you want more help with this.

Enjoy!

facebooktwittergoogle_pluspinterestlinkedin
linkedin
Tagged , , , , , . Bookmark the permalink.

7 Responses to Zoom to point and scale (kineticjs+mousewheel)

  1. mudpunch says:

    How would you implement mobile touch scaling?

  2. It seems that some people have already made progress with this:
    kineticjs-and-pinch-and-zoom
    or
    html5-canvas-multi-touch-scale-stage-with-kineticjs

    All you would have to do then is to apply the code in my tutorial that detects the position of the mousewheel/pinch/tap/touchevent and replace that.

    I hope it helps!

  3. mudpunch says:

    Thanks a bunch!

  4. israel says:

    Hello,

    I have problems with fireFox, not work for me.

    Any help.

    Thank you.

  5. Yes it seems that Firefox uses its own event listener. As explained here: http://stackoverflow.com/questions/12468744/mousewheel-plugin-not-working-in-firefox

    I have Updated the Codepen example to work in firefox also!
    I hope it helps…

  6. Christian says:

    Thanks for this. The online sample actually doesn’t work for me, but I was able to make it work in my code, for images. My issue now is, it only works if the image has not been rotated. How would I make it work for a rotated image ?

  7. Well the rotated images have their x,y and width and height also rotated, I guess you would have to calculate their current x,y and add some offset. Or you could add a wrapper element around the images and rotate them in there and then calculate based on the wrapper rather than the image it-self (because the wrapper would not have alternate x,y). I hope it helps.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>