ajith kumar
ajith kumar

Reputation: 361

Fabric.js: Grayout other portion of image other than the selected portion

In the below Image I have three boxes in the canvas and there are three buttons at the bottom of the image. Whenever I click a button, the corresponding object in the canvas gets selected(i,e, when I click the green button, green rectangle in the canvas, gets selected).

My requirement is to highlight only the selected portion and other portion of the canvas should be grayed out. (Ex: If I click the green button green rectangle should be selected and other portion should be overlayed with a gray background).

Js Fiddle Link: https://jsfiddle.net/rnvs2hdk/1/

var canvas = new fabric.Canvas('c');
canvas.backgroundColor = 'yellow';
var li= []

canvas.renderAll();

fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function(myImg) {
 var img1 = myImg.set({ left: 0, top: 0 ,width:400,height:500});
 canvas.add(img1); 
 var green = new fabric.Rect({
       left: 50,
       top: 50,
       width: 50,
       height: 50,
       fill: 'rgba(255,255,255,1)',
       stroke: 'rgba(34,177,76,1)',
       strokeWidth: 5,
       name:"green"
  });
  var yellow = new fabric.Rect({
       left: 150,
       top: 50,
       width: 50,
       height: 50,
       fill: 'rgba(255,255,255,1)',
       stroke: 'rgba(255,255,0,1)',
       strokeWidth: 5,
       name:"yellow"
  });
  var red = new fabric.Rect({
       left: 250,
       top: 50,
       width: 50,
       height: 50,
       fill: 'rgba(255,255,255,1)',
       stroke: 'rgba(255,0,0,1)',
       strokeWidth: 5,
       name:"red"
  });
   canvas.add(green, yellow,red);
   li.push(green);
   li.push(yellow);
   li.push(red);
   li.some(v=>{
     var btn = document.createElement("BUTTON");   // Create a <button> elem
         btn.innerHTML = v.name;     
     btn.addEventListener('click',function(e){
        var name = e.target
      if(name.innerText == "green"){
        canvas.setActiveObject(li[0]);
      }
      if(name.innerText == "yellow"){
        canvas.setActiveObject(li[1]);
      }
      if(name.innerText == "red"){
        canvas.setActiveObject(li[2]);
      }
     });// Insert text
            document.body.appendChild(btn);  
   });
   console.log(li);
});


enter image description here

Expected Result:(example) enter image description here

Upvotes: 3

Views: 950

Answers (2)

melchiar
melchiar

Reputation: 2862

Here's my solution. Using the after:render event, you can perform canvas draw actions over each frame after it is rendered. This approach has the benefit of avoiding having to create and destroy fabric objects as needed which is an expensive operation.

Be sure to call the .setCoords() method during actions like scaling and moving so that objects will update their position information while performing these actions.

var canvas = new fabric.Canvas('c');
canvas.backgroundColor = 'yellow';
var li = [];

canvas.renderAll();

fabric.Image.fromURL('http://fabricjs.com/assets/pug_small.jpg', function(myImg) {
  var img1 = myImg.set({
    left: 0,
    top: 0,
    width: 400,
    height: 500
  });
  canvas.add(img1);
  var green = new fabric.Rect({
    left: 50,
    top: 50,
    width: 50,
    height: 50,
    fill: 'rgba(255,255,255,1)',
    stroke: 'rgba(34,177,76,1)',
    strokeWidth: 5,
    name: "green",
    hasRotatingPoint: false
  });
  var yellow = new fabric.Rect({
    left: 150,
    top: 50,
    width: 50,
    height: 50,
    fill: 'rgba(255,255,255,1)',
    stroke: 'rgba(255,255,0,1)',
    strokeWidth: 5,
    name: "yellow",
    hasRotatingPoint: false
  });
  var red = new fabric.Rect({
    left: 250,
    top: 50,
    width: 50,
    height: 50,
    fill: 'rgba(255,255,255,1)',
    stroke: 'rgba(255,0,0,1)',
    strokeWidth: 5,
    name: "red",
    hasRotatingPoint: false
  });
  canvas.add(green, yellow, red);
  li.push(green);
  li.push(yellow);
  li.push(red);
  li.some(v => {
    var btn = document.createElement("BUTTON"); // Create a <button> elem
    btn.innerHTML = v.name;
    btn.addEventListener('click', function(e) {
      var name = e.target
      if (name.innerText == "green") {
        canvas.setActiveObject(li[0]);
      }
      if (name.innerText == "yellow") {
        canvas.setActiveObject(li[1]);
      }
      if (name.innerText == "red") {
        canvas.setActiveObject(li[2]);
      }
    }); // Insert text
    document.body.appendChild(btn);
  });
  console.log(li);
});

canvas.on({
  'object:moving': function(e) {
    //makes objects update their coordinates while being moved
    e.target.setCoords();
  },
  'object:scaling': function(e) {
    //makes objects update their coordinates while being scaled
    e.target.setCoords();
  }
});

//the after:render event allows you to perform a draw function on each frame after it is rendered
canvas.on('after:render', function() {

  var ctx = canvas.contextContainer,
    obj = canvas.getActiveObject();

  if (obj) {
    //set the fill color of the overlay
    ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
    var bound = obj.getBoundingRect();
    ctx.beginPath();
    //draw rectangle to the left of the selection
    ctx.rect(0, 0, bound.left, canvas.height);
    //draw rectangle to the right of the selection
    ctx.rect(bound.left + bound.width, 0, canvas.width - bound.left - bound.width, canvas.height);
    //draw rectangle above the selection
    ctx.rect(bound.left, 0, bound.width, bound.top);
    //draw rectangle below the selection
    ctx.rect(bound.left, bound.top + bound.height, bound.width, canvas.height - bound.top - bound.height)
    ctx.fill();
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.5.0/fabric.min.js"></script>
<canvas id="c" width="400" height="400"></canvas>
<div id="bt"></div>

Upvotes: 2

ajith kumar
ajith kumar

Reputation: 361

I have Accomplished this creating 4 more rectangles around the actual rectangle. I have grayed out the outer rectangles so it gives the overlay effect. Also, I will delete all the rectangles before creating a new one.

let blankColor = "rgba(0,0,0,0)";
let grayOut = "rgba(0,0,0,0.4)";
let rect = createRect(x, y, width, height, 1, blankColor);
let rect1 = createRect(0, 0, getViewPortDimensions()[0], y, 0, grayOut);
let rect2 = createRect(0, y + height, getViewPortDimensions()[0], getViewPortDimensions()[1] - (y + height), 0, grayOut);
let rect3 = createRect(0, y, x, height, 0, grayOut);
let rect4 = createRect(x + width, y, getViewPortDimensions()[0] - (x + width), height, 0, grayOut);
state.canvas.add(rect, rect1, rect2, rect3, rect4);

Deleting the already drawn rectangle:

state.canvas.forEachObject((o, index) => {
  if (index != 0) {
    state.canvas.remove(o);
  }
})

Upvotes: 1

Related Questions