skip to Main Content

I am trying to add fabric.js to an empty Blazor server-server side app. I did the following:

Added the following scripts in _Host.cshtml

<script src="https://unpkg.com/fabric@latest/dist/fabric.js"></script>
<script src="~/js/ImageEditor.js"></script>

Created this file under wwwroot/js:

window.fabricFunctions = {
    setup: function() {
        var canvas = new fabric.Canvas('canvasId');

        // create a rect object
        var deleteIcon = "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='utf-8'%3F%3E%3C!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'%3E%3Csvg version='1.1' id='Ebene_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='595.275px' height='595.275px' viewBox='200 215 230 470' xml:space='preserve'%3E%3Ccircle style='fill:%23F44336;' cx='299.76' cy='439.067' r='218.516'/%3E%3Cg%3E%3Crect x='267.162' y='307.978' transform='matrix(0.7071 -0.7071 0.7071 0.7071 -222.6202 340.6915)' style='fill:white;' width='65.545' height='262.18'/%3E%3Crect x='266.988' y='308.153' transform='matrix(0.7071 0.7071 -0.7071 0.7071 398.3889 -83.3116)' style='fill:white;' width='65.544' height='262.179'/%3E%3C/g%3E%3C/svg%3E";

        var img = document.createElement('img');
        img.src = deleteIcon;

        fabric.Object.prototype.transparentCorners = false;
        fabric.Object.prototype.cornerColor = 'blue';
        fabric.Object.prototype.cornerStyle = 'circle';

        return canvas;
    },

    add: function (canvas) {
        var rect = new fabric.Rect({
            left: 100,
            top: 50,
            fill: 'yellow',
            width: 200,
            height: 100,
            objectCaching: false,
            stroke: 'lightgreen',
            strokeWidth: 4,
        });

        canvas.add(rect);
        canvas.setActiveObject(rect);
    }
};

Placed the following in a razor page:

@page "/"

@inject IJSRuntime jsRuntime;

<div class="controls">
    <p>
        <button id="add" @onclick="Add">Add a rectangle</button>
    </p>
</div>
<canvas id="canvasId" width="400" height="300" style="border:1px solid #ccc"></canvas>

@code {
    private async Task Add()
    {
        var canvas = await jsRuntime.InvokeAsync<object>("fabricFunctions.setup");
        await jsRuntime.InvokeVoidAsync("fabricFunctions.add", canvas);
    }
}

After calling setup, I do have a canvas object that I pass to the add function. But I get an exception when calling canvas.add(rect), stating the add is not a function of canvas.

Is there a better way to keep an instance of canvas in the javascript code instead of passing it around the methods?

2

Answers


  1. See here:

    How to get JS object and pass it back?.

    If you want to pass an object by reference from one JavaScript function to another, the workaround is to create a JavaScript function to do this directly, e.g.:

    setupAndAdd: function () {
        var canvas = this.setup();
        this.add(canvas);
    }
    

    Blazor Component:

    private async Task Add()
    {
        await JSRuntime.InvokeVoidAsync("fabricFunctions.setupAndAdd");
    }
    
    Login or Signup to reply.
  2. It seems like you’re facing an issue where the canvas object passed to the add function is not recognized as a valid Fabric.js canvas instance. This might be due to the way JavaScript and Blazor interoperate. To address this issue and improve the organization of your code, you can make a few adjustments:

    1.Separate JavaScript Initialization:
    Move the canvas setup and any other related initialization code into the ImageEditor.js file. This way, you ensure that the canvas instance is properly created and maintained within the JavaScript scope.

    2. Global Scope for Canvas:
    Instead of returning the canvas instance from the setup function, create a global variable within the JavaScript scope to hold the canvas instance. This will allow other functions to access the canvas instance without passing it as a parameter.

    Here’s how you can modify your code:

    In ImageEditor.js:

    var canvas; // Declare a global variable to hold the canvas instance
    
    window.fabricFunctions = {
        setup: function() {
            canvas = new fabric.Canvas('canvasId');
            // Other initialization code
    
            return true; // Return a flag to indicate successful setup
        },
    
        add: function() {
            var rect = new fabric.Rect({
                left: 100,
                top: 50,
                fill: 'yellow',
                width: 200,
                height: 100,
                objectCaching: false,
                stroke: 'lightgreen',
                strokeWidth: 4,
            });
    
            canvas.add(rect);
            canvas.setActiveObject(rect);
        }
    };
    

    In your Blazor component:

    @page "/"
    
    @inject IJSRuntime jsRuntime;
    
    <div class="controls">
        <p>
            <button id="add" @onclick="Add">Add a rectangle</button>
        </p>
    </div>
    <canvas id="canvasId" width="400" height="300" style="border:1px solid #ccc"></canvas>
    
    @code {
        private async Task Add()
        {
            var success = await jsRuntime.InvokeAsync<bool>("fabricFunctions.setup");
            if (success)
            {
                await jsRuntime.InvokeVoidAsync("fabricFunctions.add");
            }
        }
    }
    

    This approach maintains the canvas instance within the JavaScript scope without needing to pass it explicitly between functions. Just be cautious with using global variables, as they can lead to unexpected behavior in more complex scenarios. In this case, since you’re dealing with a single canvas instance, it should work well.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search