Вопрос

I am using FabricJS to put SVG objects on Canvas element in the HTML. But since the FabricJS uses new keyword to instantiate classes, I think the properties of that class are getting tied to the global namespace.

Below, is my code for reference

My JSON object that I am parsing

var defaultSceneObj = [
        {
            "eyes": "res/img/animals/cat/cat_part_eye.svg",
            "skin": "res/img/animals/cat/cat_skin.svg",
            "mouth": "res/img/animals/cat/cat_part_mouth_happy.svg",
            "pos": {
                "ground" : "right_back", 
                "sky" : "none", //other values ["none"]
                "relative" : "none" //other values ["none", "top", "bottom"]
            }  
        },
        {
            "eyes": "res/img/animals/cat/cat_part_eye.svg",
            "skin": "res/img/animals/cat/cat_skin.svg",
            "mouth": "res/img/animals/cat/cat_part_mouth_happy.svg",
            "pos": {
                "ground" : "left_back", 
                "sky" : "none", //other values ["none"]
                "relative" : "none" //other values ["none", "top", "bottom"]
            }  
        }
    ];

Which means there are 2 animals in my object, where each animal is composed of eye, skin and mouth svg files.

I am looping through them in my javascript code to render them

var renderObjOnCanvas = function(cObj, cDim){
        // console.log("Object, Dimension:", cObj, cDim);
        // var canvas = new fabric.Canvas('elem-frame-svg');
        var canvas = this.__canvas = new fabric.Canvas('elem-frame-svg');

        imgwidth = 200; //default image width
        imgheight = 255; //default image height
        imgScale = 0.6;
        imgOffsetX = Math.floor(imgwidth*imgScale/2);
        imgOffsetY = Math.floor(imgheight*imgScale/2);

        canvaswidth = canvas.width;
        canvasheight = canvas.height;

        // console.log("render canvas dimensions:", canvaswidth, canvasheight); 
        if (cObj.length > 0){

            for (var c =0; c < cObj.length; c++){
                var noun = cObj[c]; //assign the noun object

                if (noun.skin !== 'Undefined'){
                    var animalParts = ['skin', 'eyes', 'mouth'];
                    var pos = cDim.ground[noun.pos.ground];

                    for (var g = 0; g < animalParts.length; g++){

                        var part_top = canvasheight - (pos[1] + imgOffsetY);
                        var part_left = pos[0] - imgOffsetX;
                        console.log("part:", noun[animalParts[g]], "part_position: ", part_top, part_left);

                        var img = new fabric.Image.fromURL(noun[animalParts[g]], function(s){
                            this.top = part_top;
                            this.left = part_left;
                            // this.scale(imgScale);

                            s = this;

                            console.log("part:", part_top, part_left);

                            canvas.add();

                        }); 
                    }
                }
            }   
        }
    };

The first console.log outputs the correct top and left coordinates, but the second one only outputs the last values assigned and hence all my objects are getting placed on the same position in canvas.

Output for the first console.log:

part: res/img/animals/cat/cat_skin.svg part_position:  282 574 main.js:126
part: res/img/animals/cat/cat_part_eye.svg part_position:  282 574 main.js:126
part: res/img/animals/cat/cat_part_mouth_happy.svg part_position:  282 574 main.js:126
part: res/img/animals/cat/cat_skin.svg part_position:  282 135 main.js:126
part: res/img/animals/cat/cat_part_eye.svg part_position:  282 135 main.js:126
part: res/img/animals/cat/cat_part_mouth_happy.svg part_position:  282 135

Output for the second console.log:

(6) part: 282 135 
Это было полезно?

Решение

It's because the forEach manages the scope for the variables for you and whereas for does not. For example,

var arr = [1,2,3];
for (var i=0;i<arr.length;i++){
   var r = 100;
}
console.log(r); //prints 100 

arr.forEach(function(){
  var w = 100;
});
console.log(w); //prints "w is not defined"

so in your case, the part_top, part_left variables exists outside the for loop and only the last assigned value will be taken up by the call back function as variables are passed by reference. Take a look this answer

scope of variables in JavaScript callback functions

Другие советы

Using forEach() method instead of for loop worked for me. Although I am not sure, why

Our closest explanation is that since forEach() accepts an anonymous function, it binds the scope of variables into a closure when the new operator is invoked.

...
if (noun.skin !== 'Undefined'){
                    var animalParts = ['skin', 'eyes', 'mouth'];
                    var pos = cDim.ground[noun.pos.ground];

                    animalParts.forEach(function(item, g){ // <-works
                    // for (var g = 0; g < animalParts.length; g++){

                        var part_top = canvasheight - (pos[1] + imgOffsetY);
                        var part_left = pos[0] - imgOffsetX;
                        console.log("part:", noun[animalParts[g]], "part_position: ", part_top, part_left);

                        var img = new fabric.Image.fromURL(noun[animalParts[g]], function(s){
                            s.top = part_top;
                            s.left = part_left;
                            s.scale(imgScale);
                            // console.log(s, s.top,s.left, part_top, part_left);
                            canvas.add(s);
                        });
                    });
                }
...
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top