Skip to main content

Command Palette

Search for a command to run...

How we implemented multiselect in the Ablo Editor with fabric.js

ActiveSelection class enabled intuitive multi-object manipulation transforming the editor experience

Updated
7 min read
How we implemented multiselect in the Ablo Editor with fabric.js

1. Introduction

One of the most intuitive features in modern design tools is the ability to select and manipulate multiple objects simultaneously. Whether you're moving several graphics together, changing their size or deleting a group of items at once, multiselect dramatically improves workflow efficiency.

In this article, we'll explore how we implemented multiselect functionality in the Ablo editor, the challenges we faced, and why this feature makes the editing experience so much more powerful and intuitive.

2. Before Multiselect: The Single-Selection Era

Previously, the Ablo editor operated in a single-selection mode. Users could only interact with one object at a time on the canvas. This meant that common workflows like:

  • Moving multiple graphics together

  • Deleting a group of objects

  • Copying multiple items at once

...required users to perform these operations one object at a time, which was tedious and time-consuming. Each operation required clicking an object, performing the action, then repeating for the next object.

3. How Single Selection Worked Under The Hood

In the single-selection implementation, object selection was handled through mouse event listeners. When a user clicked on the canvas, the system would:

  • Detect the click target: On mouse:up events, the code checked if the click hit an object (e.target)

  • Set the active object in Canvas: If an object was clicked and it was selectable, it would be set as the active object:

    •     canvas.on('mouse:up', function (e) {
            if (e.target?.selectable) {
               canvas.setActiveObject(e.target);
               canvas.renderAll();
            }
          });
      
  • Update React state: The active object was then stored in React state and used throughout the editor.

    •     const activeObj = canvas.getActiveObject();
          setActiveObject(activeObj || null);
      
  • Handle deselection: Clicking on empty canvas space would deselect the current object, clearing the active state

This approach worked well for single-object interactions, but it meant that selecting multiple objects required a sequential process: click object A, perform action, click object B, perform action, and so on. There was no way to select multiple objects simultaneously and operate on them as a group.

4. The Solution: Fabric.js ActiveSelection

Fabric.js, the powerful canvas library that powers our editor, provides a built-in solution for multiselect through the ActiveSelection class. This class represents a temporary group of selected objects that can be manipulated together without permanently grouping them.

The key insight is that ActiveSelection behaves like a regular fabric object in many ways, it can be moved, scaled, rotated, and transformed, but it's actually a container that holds references to multiple objects. When you perform operations on an ActiveSelection, fabric.js intelligently applies those operations to all contained objects.

4.1. Enable Selection on fabric.js canvas

The first step was to enable canvas selection specifically for desktop users. In our canvas initialization code, we conditionally enable selection based on the device type:

  const isCanvasSelectionEnabled = isSelectionEnabled && !isMobile;

  canvas = new fabric.Canvas(`${canvasName}`, {
    width,
    height,
    selection: isCanvasSelectionEnabled,
    ...
  });

This ensures that:

  • Desktop users get the full multiselect experience with drag-to-select and modifier key support

  • Mobile users maintain the touch-friendly single-tap selection behavior

  • The feature can be toggled if needed via the isSelectionEnabled parameter for most mini-versions of the editor

4.2. Why fabric.js `getActiveObject()` can Handle both Single & Multi Select

A key aspect of fabric.js's selection system is that canvas.getActiveObject() can return either a single fabric.Object or a fabric.ActiveSelection, depending on how many objects are currently selected. This is handled automatically by fabric.js:

  • Single object selected: When a user clicks on one object, getActiveObject() returns that object directly (e.g., fabric.Image, fabric.IText, etc.)

  • Multiple objects selected: When a user drags to select multiple objects or uses modifier keys to add objects to the selection, fabric.js automatically creates an ActiveSelection instance that wraps all selected objects. In this case, getActiveObject() returns the ActiveSelection container.

This design means we don't need to manually track which objects are selected, as fabric.js manages the selection state internally. When selection is enabled on the canvas (selection: true), fabric.js handles the complexity of:

  • Creating selection rectangles when dragging

  • Managing modifier key behavior (Shift + Click for adding to selection)

  • Automatically wrapping multiple selections in an ActiveSelection

  • Returning the appropriate type based on selection count

This is why we can simply call canvas.getActiveObject() anywhere in our code and check the type. Fabric.js has already done the heavy lifting of managing the selection state.

4.3. Type System Updates

To properly handle multiselect throughout our codebase, we updated our type definitions to recognize ActiveSelection as a valid active object type:

  const [activeObject, setActiveObject] = useState<fabric.Object | fabric.ActiveSelection | null>(null);

4.4. Detecting and Normalizing Selections

Throughout the codebase, we use a consistent pattern to detect when the user has selected multiple objects and normalize them for processing:

const isMultiSelect = activeObject instanceof fabric.ActiveSelection;
const objectsToProcess: fabric.Object[] = isMultiSelect
    ? activeObject.getObjects()
    : [activeObject];

The key insight of this pattern is that it serializes the selection into an array format, regardless of whether we're dealing with a single object or multiple objects. This normalization allows us to:

  • Check if we're dealing with a multiselect using instanceof fabric.ActiveSelection

  • Extract the individual objects, either from the ActiveSelection container or wrap the single object in an array

  • Process all objects uniformly using array methods like forEach, map, or filter

By converting both cases into an array, we can write operation logic once that works for both single and multiple selections, making the code more maintainable and consistent.

5. The Challenges: Not So Easy As It Sounds

5.1. Mobile Considerations

One of the first decisions we had to make was how multiselect should work on mobile devices. We decided to move fast and ship multiselect as a Desktop-Only feature for the first version.

Why? Mobile devices have different interaction patterns, touch gestures, smaller screens, and different user expectations. The drag-to-select behavior that works so well on desktop with a mouse doesn't translate naturally to touch interfaces and might conflict with like pan gestures.

By keeping multiselect desktop-only initially, we could:

  • Focus on perfecting the desktop experience first

  • Avoid complicating the mobile touch interactions

  • Maintain the existing, optimized mobile selection behavior

This decision allowed us to ship a polished desktop feature faster while keeping the door open for future mobile enhancements if user feedback indicates a need.

5.2. Operation Consistency

One of the main challenges was ensuring that operations like copy, delete, and layer management work correctly with multiple objects. For example, when copying multiple objects, we need to preserve their relative positions and transformations.

Solution: We handle this by checking for multiselect and processing all objects in the selection one by one:

  const handleRemoveActiveObject = () => {
    const activeObject = canvas.getActiveObject();

    const objectsToProcess =
      activeObject instanceof fabric.ActiveSelection ? activeObject.getObjects() : [activeObject];

    objectsToProcess.forEach((obj: fabric.Object) => {
      canvas.remove(obj);
    });

    canvas.discardActiveObject();
    canvas.renderAll();

    saveState();
  };

This pattern respects the DRY principle by normalizing the selection into an array format upfront. Instead of having conditional checks scattered throughout the codebase, e.g. checking if (isMultiSelect) in every operation, we perform a single check at the beginning and then process all objects uniformly.

5.3. Complex Operations and Scope Management

Not all operations make sense or are feasible with multiselect. Some features, like cropping, are inherently single-object operations that don't translate well to multiple selections.

Decision 1: We left certain operations out of scope for the first version. For example, the crop tool is disabled when multiple objects are selected:

Decision 2: Some operations like copying, need a totally different implementation for single & multiselect and can’t be processed by the exact same algorithms.

6. Why It’s Awesome

A. Intuitive Desktop Experience: Multiselect works exactly as users expect from modern design tools. You can:

  • Drag to select: Click and drag on the canvas to create a selection rectangle

  • Modifier keys: Hold Shift and click to add objects to your selection

  • Visual feedback: Selected objects are clearly highlighted with selection handles

This matches the behavior users are familiar with from modern tools like Figma.

B. Workflow Efficiency: The time savings are significant. Consider a scenario where you need to:

  • Move 5 graphics to a new position

  • Delete 4 unwanted objects

Before: 9 separate operations (select, act, repeat), After: 2 operations (multiselect, act, done) ###

C. Preserves Relationships and Alignment: One of the most powerful aspects of multiselect is how it maintains the spatial relationships between objects. When you select multiple elements and move them together, their relative positions are preserved perfectly.

7. Conclusion

The multiselect feature represents a significant improvement in the Ablo editor's usability. By leveraging fabric.js's ActiveSelection class and thoughtfully handling edge cases, we've created an intuitive, powerful feature that matches user expectations from modern design tools.

The implementation demonstrates how a well-designed library feature (fabric.js's ActiveSelection) can be integrated into a complex application. The result is a feature that feels natural, saves users time, and makes the editor more powerful without adding complexity to the user interface.