Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pinch to zoom on canvas

I'm trying to set a simple zoom in/out functionality on a canvas. I'm using KineticJS to handle touch events and drawing in canvas, but couldn't implement the zoom. KinteicJS have a similar example, but they always zoom on the center while I'm intrested to zoom on the point between the fingers.

Thank you!

like image 461
KaBoom Avatar asked Jun 06 '12 21:06

KaBoom


1 Answers

A comprehensive sample I wrote for a project showing:
- desktop zooming around mouse point using scrollwheel
- crossbrowser scrollwheel use, including firefox (using jquery.mousewheel.js)
- iPAD zooming using pinch around point between fingers
- support for iPAD orientation change and disabling safari auto zoom
- zooming one layer while keeping other layers not zoomed
- OOP javascript 'class' and proxying events to local methods

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8">
  <script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.7.4.min.js"></script>
  <script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
  <script src="https://rawgithub.com/brandonaaron/jquery-mousewheel/master/jquery.mousewheel.js"></script>
</head>
<body>
  <div id="container"></div>

<script type='text/javascript'>
function makePos(x, y) {
    return {
        'x': x,
        'y': y
    };
}


function View() {
    this.init = init;

    var self = this;

    var kineticStage,
        kineticLayer,
        kineticTextLayer,

        zoomOrigin = makePos(0, 0),
        zoomFactor = 1.1,
        pinchLastDist, pinchStartCenter;

    init();

    function init() {

        // disable ipad zoom to prevent safari from zooming on orientation change
        $('head meta[name=viewport]').remove();
        $('head').prepend('<meta name="viewport" content="user-scalable=no, width=device-width, minimum-scale=1.0 , initial-scale=1.0, maximum-scale=1.0" />');

        // create stage layer and circle
        kineticStage = new Kinetic.Stage({
            container: 'container',
            width: $(document).width() - 10,
            height: $(document).height() - 10,
        });

        kineticLayer = new Kinetic.Layer({
            draggable: true
        });
        var circle = new Kinetic.Circle({
            x: 200,
            y: 200,
            radius: 50,
            fill: '#00D200',
            stroke: 'black',
            strokeWidth: 2,
        });
        kineticLayer.add(circle);
        kineticStage.add(kineticLayer);

        kineticTextLayer = new Kinetic.Layer({});
        kineticStage.add(kineticTextLayer);

        // register events and proxy to methods
        $(kineticStage.content).on('mousewheel', function(e) {
            e.preventDefault();
            var evt = e.originalEvent;
            stageMouseWheel.call(self, evt.deltaY, makePos(evt.clientX, evt.clientY));
        });
        kineticStage.getContent().addEventListener('touchmove', function(e) {
            e.preventDefault(); // prevent iPAD panning
            var touch1 = e.touches[0];
            var touch2 = e.touches[1];
            if (touch1 && touch2) {
            touch1.offsetX = touch1.pageX - $(touch1.target).offset().left;
            touch1.offsetY = touch1.pageY - $(touch1.target).offset().top;
            touch2.offsetX = touch2.pageX - $(touch2.target).offset().left;
            touch2.offsetY = touch2.pageY - $(touch2.target).offset().top;
            stagePinch.call(self, makePos(touch1.offsetX, touch1.offsetY), makePos(touch2.offsetX, touch2.offsetY));
            }
        }, false);
        kineticStage.getContent().addEventListener('touchend', function(e) {
            stageTouchEnd.call(self);
        }, false);

        $(window).on("orientationchange", function(event) {
            window.scrollTo(0, 0); // scroll to top left on orientation change
        });

        editTextLayer('scrollwheel/pinch to zoom');
        window.scrollTo(0, 0);
    }

    function zoom(newscale, center) { // zoom around center
        var mx = center.x - kineticLayer.getX(),
            my = center.y - kineticLayer.getY(),
            oldscale = kineticLayer.getScaleX();
        editTextLayer('zoom ' + newscale.toFixed(2) + ' around ' + mx.toFixed(2) + ',' + my.toFixed(2));

        zoomOrigin = makePos(mx / oldscale + zoomOrigin.x - mx / newscale, my / oldscale + zoomOrigin.y - my / newscale);

        kineticLayer.setOffset(zoomOrigin.x, zoomOrigin.y);
        kineticLayer.setScale(newscale);
        kineticLayer.draw();
    }

    function stageMouseWheel(factor, p) {
        var oldscale = kineticLayer.getScaleX(),
            newscale = oldscale * (zoomFactor - (factor < 0 ? 0.2 : 0));
        zoom(newscale, p);
    }

    function stagePinch(p1, p2) {
        var dist = Math.sqrt(Math.pow((p2.x - p1.x), 2) + Math.pow((p2.y - p1.y), 2));
        if (!pinchLastDist) pinchLastDist = dist;
        var newscale = kineticLayer.getScale().x * dist / pinchLastDist;

        var center = makePos(Math.abs((p1.x + p2.x) / 2), Math.abs((p1.y + p2.y) / 2));
        if (!pinchStartCenter) pinchStartCenter = center;

        zoom(newscale, pinchStartCenter);
        pinchLastDist = dist;
    }

    function stageTouchEnd() {
        pinchLastDist = pinchStartCenter = 0;
    }

    function editTextLayer(text) {
        var label = new Kinetic.Text({
            x: 0,
            y: 0,
            text: text,
            fontSize: 12,
            fontFamily: 'Calibri',
            fill: 'black'
        });

        kineticTextLayer.destroyChildren();
        kineticTextLayer.add(label);
        kineticTextLayer.drawScene();
    }
}


$(window).load(function() {
    var view = new View();
}); 
</script>

</body>
</html>

http://jsfiddle.net/45YJH/1/

like image 68
kofifus Avatar answered Sep 28 '22 02:09

kofifus