0

Can you help me figure out what is going on here.

Basically I want to use FabricJS with ReactJS and Redux. I want to store the currently selected objects in redux store. But it seems it has a weird behavior when it comes to saving the active objects of multi selection.

let selectedObjects = null;
let canvas = new fabric.Canvas('canvas-area', {
  preserveObjectStacking: true,
  width: 500,
  height: 500
});

canvas.add(new fabric.Rect({
  left: 100,
  top: 100,
  fill: 'blue',
  width: 100,
  height: 100
}));

canvas.add(new fabric.Circle({
  radius: 50,
  fill: 'green',
  left: 200,
  top: 200
}));

canvas.renderAll();

// =======================

document.querySelector('#save-selection').addEventListener('click', ()=> {
  //selectedObjects = Object.assign({}, canvas.getActiveObject());
  selectedObjects = canvas.getActiveObject();
  canvas.discardActiveObject();
  canvas.renderAll();
});

document.querySelector('#apply-saved-selection').addEventListener('click', ()=> {
 canvas.setActiveObject(selectedObjects);
  canvas.renderAll();
});
<html>
  <body>
    <button id='save-selection'>Save Selection</button>
    <button id='apply-saved-selection'>Apply Saved Selection</button>
    <canvas id='canvas-area'></canvas>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.3/fabric.min.js"></script>
  </body>
</html>

Basically the way you use the code snippet above are:

Use Case 1 : Single selected object

  1. Select a single object.
  2. Click Save Selection ( it will save the current selected object and clear current canvas selection ).
  3. Click Apply Saved Selection.
  4. Drag the currently selected object, the selection and the selected object stays in synced and the app works as expected.

Use Case 2 : Multiple selection

  1. Select multiple objects.
  2. Click Save Selection.
  3. Click Apply Saved Selection.
  4. Drag the currently selected objects, as you can see, only the selection is dragged, the actual selected objects are left behind (unsynced).

Can you help me figure out how to properly saved the selected object/s for later use?

Thanks in advance

Note: I'm using latest FabricJS (2.3.3)

Jplus2
  • 2,216
  • 2
  • 28
  • 49

1 Answers1

1

Update:

It is not wise to store selected elements in redux store. It is very hard to reload saved selected elements specially when we handle grouped selection, grouped objects, clipped images, etc...

Bottom Line, Do not save selected elements in redux store.

==================

Update:

My answer below have a flaw, and that is the rotation angle of the selection is not preserved when saved rotated selection is being loaded.

Still looking for a much better answer than what I currently have.

==================

Ok so it seems we need to have different approach in reloading multi selected objects.

Summary

We need to create a new selection using the exact objects in the canvas that correspond to the objects inside the saved selection.

// Extend fabric js so that all objects inside the canvas
// will have the custom id attribute
// We will use this later to determine which is which
fabric.Object.prototype.id = undefined;

let selectedObjects = null;
let canvas = new fabric.Canvas('canvas-area', {
  preserveObjectStacking: true,
  width: 500,
  height: 500
});

canvas.add(new fabric.Rect({
  id: 'unique-id-01',
  left: 100,
  top: 100,
  fill: 'blue',
  width: 100,
  height: 100
}));
canvas.add(new fabric.Circle({
  id: 'unique-id-02',
  radius: 50,
  fill: 'green',
  left: 200,
  top: 200
}));
canvas.add(new fabric.Rect({
  id: 'unique-id-03',
  left: 300,
  top: 300,
  fill: 'red',
  width: 100,
  height: 100
}));
canvas.renderAll();


// =======================

document.querySelector('#save-selection').addEventListener('click', ()=> {
  //selectedObjects = Object.assign({}, canvas.getActiveObject());
  selectedObjects = canvas.getActiveObject();
  canvas.discardActiveObject();
  canvas.renderAll();
});

document.querySelector('#apply-saved-selection').addEventListener('click', ()=> {

  if (selectedObjects.type === 'activeSelection') {
   
    let canvasObjects = canvas.getObjects(); // Get all the objects in the canvas
    let selObjs = [];
    
    // Loop through all the selected objects
    // Then loop through all the objects in the canvas
    // Then check the id of each objects if they matched
    // Then store the matching objects to the array
    for (let so of selectedObjects._objects) {
     for (let obj of canvasObjects) {
       if (obj.id === so.id) {
         selObjs.push(obj); // Store the canvasObjects item, not the selectedObjects item.
          break;
        }
      }
    }
    
    // Create a new selection instance using the filtered objects above
    let selection = new fabric.ActiveSelection(selObjs, {
     canvas: canvas
    });
    
    canvas.setActiveObject(selection); // Profit
    
  } else { // Single object selected, load directly
   canvas.setActiveObject(selectedObjects);
  }
  
  canvas.renderAll();
});
<html>
  <body>
    <button id='save-selection'>Save Selection</button>
    <button id='apply-saved-selection'>Apply Saved Selection</button>
    <canvas id='canvas-area'></canvas>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/2.3.3/fabric.min.js"></script>
  </body>
</html>
  1. We need to extend fabric js and add custom id attribute. We will use this later to compare between the objects inside the fabric js instance and the objects inside the saved selection.

  2. Loop through the fabric js instance objects and the saved selection objects and determine which of the objects in fabric js instance are selected by using the custom id we added earlier.

  3. Create new selection instance, new fabric.ActiveSelection, using the filtered objects in step 2.

  4. Set the fabric.ActiveSelection instance as the activeObject of the fabric js instance.

  5. Profit

Fabric JS Selection Handling Reference

Jplus2
  • 2,216
  • 2
  • 28
  • 49