0

I have 2 layers, and I would like to make holes in the front layer to let the user see the back layer. For this purpose, I'm drawing circles in a canvas, whitch is placed between the 2 layers (don't know if its place is correct).

For demonstration purpose, I have exploded the 3 layers, and displayed the canvas.

I have try several combinations of blend / mask, but I'm missing something here, and looking for help.

Where should be placed the canvas regarding the 2 layers, and what kind of blend operation should I make to make holes in the front layer ?

One solution should be to tell the front layer that it should use the canvas as an alpha mask when displayed.

Note : the back layer should remain untouched.

Here is a screenshot explaining the layout :

enter image description here

...and the codepen link: Mask Effect

   /***
    **  onclick(e)
    */
    $( document ).click(function(e) {
      var ctx = $("canvas")[0].getContext('2d');
      var radius = Math.random() * 50 + 10;

      ctx.beginPath();
      ctx.arc(e.clientX, e.clientY, radius, 0, 2 * Math.PI, false);
      ctx.fillStyle = 'white';
      ctx.fill();
      ctx.closePath();

    //  ctx.globalCompositeOperation = 'screen';
    //  ctx.drawImage(ctx.canvas.toDataURL("image/png"),0,0); // debug: visualize

      $('#front').css({
            'mask-image':"url(" + ctx.canvas.toDataURL("image/png")+ ")",
            'background-blend-mode':"screen",
            'background-image':"url(" + ctx.canvas.toDataURL("image/png")+ ")"
      });
soleshoe
  • 1,215
  • 2
  • 12
  • 16
  • You might invert what you're trying to do - have "container inverted clone" be transparent, and paint everything but the circle. – theGleep Oct 25 '17 at 14:29
  • 1
    You can use `ctx.globalCompositeOperation = "destination-out";` then what ever you render to the canvas will remove pixels (out) from the canvas (destination). Thus `ctx.globalCompositeOperation = "destination-out"; ctx.fillRect(10,10,10,10);` will put a 10 by 10 square hole in the canvas. The default is "source-over" – Blindman67 Oct 25 '17 at 15:46

2 Answers2

2

A solution for paths would be something like:

var width = 300;
var height = 200;

var canvas = document.getElementById( "myCanvas" );
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#FFFFFF";
ctx.fillRect( 0, 0, width, height );

canvas.onmousemove = function( e ){

  ctx.clearRect( 0, 0, width, height );

  // counter-clockwise path
  ctx.fillStyle = "#FFFFFF";
  ctx.beginPath();
  ctx.moveTo( 0, 0 );
  ctx.lineTo( 0, height );
  ctx.lineTo( width, height );
  ctx.lineTo( width, 0 );
  ctx.closePath();
  
  // clockwise path
  ctx.arc( e.clientX, e.clientY, 20, 0, 2 * Math.PI, false);

  ctx.fill();

};
#back {
  position: absolute;
  left: 0px;
  top: 0px;
  width: 300px;
  height: 200px;
  background: linear-gradient( to right, red , yellow);
  z-index: 0;
}

#front {
  position: absolute;
  left: 0px;
  top: 0px;
  width: 300px;
  height: 200px;
  z-index: 1;
  border: solid thin #000000;
}
<div id="back"></div>
<div id="front">
  <canvas id="myCanvas" width="300" height="200"/>
</div>

Take a look here to understand why if works: Masking shapes in HTML5 canvas?

davidbuzatto
  • 9,207
  • 1
  • 43
  • 50
0

All right, thanks a lot @davidbuzatto ! I was missing the clearRect() function to handle the alpha layer, after a little tweaking I found the solution :)

Actually, the solution is the exact opposite of what I was trying to do : the inverted clone is in front by default, but totally masked out. When I Click, I fill the mask with color, which lets the foreground appear.

Here is the working codepen link : https://codepen.io/SOLESHOE/pen/YrmyoX

var canvas = null;

/***
** $(document).ready()
*/
$(document).ready(function(){
  
  var el = $("#container");
  
  canvas = document.createElement('canvas');
  canvas.width = el.width();
  canvas.height = el.height();
  canvas.position = "absolute";
  
 var ctx = canvas.getContext("2d");
 ctx.clearRect( 0, 0, canvas.width, canvas.height );

 var clone = el.clone();
 clone.toggleClass('white blue');
 clone[0].setAttribute('id','front');

 clone.css({"width":el.width()+"px",
            "height":el.height()+"px",
            'mask-image':"url(" + canvas.toDataURL("image/png")+ ")",
            'position':"absolute"
            }); 
  $("body")[0].appendChild(clone[0]);
});

/***
**  onclick(e)
*/
$( document ).click(function(e) {
  var ctx = canvas.getContext('2d');
  var radius = Math.random() * 60 + 30;
  
  ctx.beginPath();
  ctx.arc(e.clientX, e.clientY, radius, 0, 2 * Math.PI, false);
  ctx.fillStyle = 'white';
  ctx.fill();
  ctx.closePath();

   $('#front').css({
        'mask-image':"url(" + canvas.toDataURL("image/png")+ ")",
   });  

});
#container {
  position: absolute;
  width: 300px;
  height: 300px;
}

.white{
  fill: white;
  color: white;
  background: blue;
}

.blue {
  fill: blue;
  color:blue;
  background: white;
}

#circle {
  margin-left: 100px;
  margin-top: 50px;
}

#text {
  text-align: center;
  font-size: 60px;
  font-weight: bolder;
  margin-top: 30px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="container" class='white'>
  <svg id="circle" width="50px" height="50px">
    <circle cx="25px" cy="25px" r="25px"></circle>
  </svg>
  <div id="text">
    HELLO WORLD!
  </div>
</div>
soleshoe
  • 1,215
  • 2
  • 12
  • 16