Search code examples
imagekineticjsmask

KineticJS - How can we move an image behind a mask without moving mask


Is it possible to move an image behind a mask without moving the mask itself ? I am looking for an operation which allow to move an image behind a mask and it should be accurate and smooth.

The best answer I am looking is to mask an Kinetic.Image object. Kinetic.Image is draggable and need to worry about it's movement. Please let me know if it's really possible to mask Kinetic.Image Object ?


Solution

  • A Demo: http://jsfiddle.net/m1erickson/u28MS/

    Use a Kinetic.Shape to get access to the canvas context and then create a clipping region.

    • Create a new Kinetic.Shape

    • Define your non-rectangular path in the shape

    • Call clip() to restrict drawing to that path.

    • Draw the image into the clipping region.

    • Give the image x & y properties so that the image can be draw

    Here's what that looks like in code:

    // create a Kinetic.Shape which gives you access
    // to a context to draw on
    
    clippingShape = new Kinetic.Shape({
        sceneFunc: function(context) {
    
          // define your path here
          // context.beginPath(); ...
    
          // make your path a clipping region
    
          context.clip();
    
          // draw the image inside the clipping region
          // img.x & img.y are offsets which can be used
          // to "drag" the image around the clipping region
    
          context.drawImage(img,img.x,img.y);
    
          // KineticJS specific context method
          context.fillStrokeShape(this);
    
        },
        stroke: 'black',
        strokeWidth: 4,
        listening:false
    });
    

    Listen for mouse events on the stage to cause the image to reposition when drawn in the Shape.

    • In mousedown: Save the mouse position and set a flag indicating the drag has begun.

    • In mousemove: Calc how much the mouse has moved and offset the image's x/y by that distance.

    • In mouseup: clear the dragging flag since the drag is over.

    The mouse event handlers look like this:

    var isdown=false;
    
    stage.getContent().onmousedown=function(e){ 
        var pos=stage.getPointerPosition();
        img.lastX=parseInt(pos.x);
        img.lastY=parseInt(pos.y);
        isdown=true; 
    };
    stage.getContent().onmouseup=function(e){ 
        isdown=false; 
    };
    stage.getContent().onmousemove=function(e){
        if(!isdown){return;}
        var pos=stage.getPointerPosition();
        var mouseX=parseInt(pos.x);
        var mouseY=parseInt(pos.y);
        var dx=mouseX-img.lastX;
        var dy=mouseY-img.lastY;
        img.lastX=mouseX;
        img.lastY=mouseY;
        img.x+=dx;
        img.y+=dy;
        layer.draw();
    };
    

    [ Previous version of answer -- replaced with new answer above after questioners commments ]

    This kind of clipping is traditionally done with a foreground image that contains a transparent "viewport" which lets the user see a portion of the background image beneath.

    A Demo: http://jsfiddle.net/m1erickson/2f9yu/

    enter image description here

    Create a draggable background image on a bottom layer:

    // create a background layer
    
    var bottomLayer=new Kinetic.Layer();
    stage.add(bottomLayer);
    
    // put a draggable image on the background layer
    
    var city=new Kinetic.Image({ image:bk,x:0,y:0,draggable:true,width:700,height:440, });
    bottomLayer.add(city);
    bottomLayer.draw();
    

    Create a non-draggable foreground image on a top layer.

    The top image has a transparent "viewport".

    Important: the top layer does not listen for events, so dragging moves the bottom image, not the top image.

    // create a top layer that does not respond to mouse events
    // any mouse events will filter down to the background image
    // this enables the background to be dragged even while behind the top image
    
    var topLayer=new Kinetic.Layer({listening:false,});
    stage.add(topLayer);
    
    // create a top image with transparent pixels 
    // used as a viewport to see a portion of the bottom image
    
    var mirror=new Kinetic.Image({ image:viewport,x:0,y:0 });
    topLayer.add(mirror);
    topLayer.draw();
    

    Example code:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>Prototype</title>
        <script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
        <script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v5.0.1.min.js"></script>
    <style>
    body{padding:20px;}
    #container{
      border:solid 1px #ccc;
      margin-top: 10px;
      width:350px;
      height:300px;
    }
    </style>        
    <script>
    $(function(){
    
        var stage = new Kinetic.Stage({
            container: 'container',
            width: 350,
            height: 300
        });
        var layer = new Kinetic.Layer();
        stage.add(layer);
    
        var bottomLayer=new Kinetic.Layer();
        stage.add(bottomLayer);
        var topLayer=new Kinetic.Layer({listening:false,});
        stage.add(topLayer);
    
        var loadedCount=0;
        //
        var bk=new Image();
        bk.onload=start;
        bk.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/desert1.jpg";
        //
        var viewport=new Image();
        viewport.onload=start;
        viewport.src="https://dl.dropboxusercontent.com/u/139992952/multple/car4.png";
    
        function start(){
            if(++loadedCount<2){return;}
    
            var city=new Kinetic.Image({ image:bk,x:0,y:0,draggable:true,width:700,height:440, });
            bottomLayer.add(city);
            bottomLayer.draw();
    
            var mirror=new Kinetic.Image({ image:viewport,x:0,y:0 });
            topLayer.add(mirror);
            topLayer.draw();
    
        }
    
    
    }); // end $(function(){});
    
    </script>       
    </head>
    <body>
        <h4>Drag to move the background image in the mirror</h4>
        <div id="container"></div>
    </body>
    </html>