easeljscreate.js

Easel.js SpriteSheetBuilder() causing browser to lock up


I have used the SpriteSheetBuilder in Easel.js to create sprite sheets on the fly. The issue I am having is that I am only able to draw about 5 or 6 instances of the sprite to the canvas before the browser locks up. It is causing significant cpu and memory issues. I am having trouble pinpointing why this is happening. It must be my implementation. My sprite sheet is comprised of about 100 frames but he images I am using are only 8kbs each.

What I am making is a conga line animation. I will be making a call to the server to get the faces that will be composited on the dancer sprites with the sprite builder. Any insight into this issue is great appreciated! Here is my code.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>EaselJS Example: SpriteSheetBuilder</title>
    <script src="easeljs-0.8.2.min.js"></script>
    <style>
        #dancers img {
            display: none;
        }

        canvas {
            background: red;
            width: 100%;
        }

        button {
            width: 50px;
            height: 50px;
        }
    </style>

</head>

<body>

<div id="dancers">
    <img id="stage" src="stage.jpg">
    <img id="curtains" src="curtains.png">
    <img id="conga_1" src="conga_1.png">
    <img id="conga_2" src="conga_2.png">
    <img id="conga_3" src="conga_3.png">
    <img id="conga_4" src="conga_4.png">
    <img id="conga_5" src="conga_5.png">
    <img id="conga_6" src="conga_6.png">
    <img id="conga_7" src="conga_7.png">
    <img id="conga_8" src="conga_8.png">
    <img id="conga_9" src="conga_9.png">
    <img id="conga_10" src="conga_10.png">
    <img id="conga_11" src="conga_11.png">
    <img id="conga_12" src="conga_12.png">
    <img id="conga_13" src="conga_13.png">
    <img id="conga_14" src="conga_14.png">
    <img id="conga_15" src="conga_15.png">
    <img id="conga_16" src="conga_16.png">
    <img id="conga_17" src="conga_17.png">
    <img id="conga_18" src="conga_18.png">
    <img id="conga_19" src="conga_19.png">
    <img id="conga_20" src="conga_20.png">
    <img id="conga_21" src="conga_21.png">
    <img id="conga_22" src="conga_22.png">
    <img id="conga_23" src="conga_23.png">
    <img id="conga_24" src="conga_24.png">
    <img id="conga_25" src="conga_25.png">
    <img id="conga_26" src="conga_26.png">
    <img id="conga_27" src="conga_27.png">
    <img id="conga_28" src="conga_28.png">
    <img id="conga_29" src="conga_29.png">
    <img id="conga_30" src="conga_30.png">
    <img id="conga_31" src="conga_31.png">
    <img id="conga_32" src="conga_32.png">
    <img id="conga_33" src="conga_33.png">
    <img id="conga_34" src="conga_34.png">
    <img id="conga_35" src="conga_35.png">
    <img id="conga_36" src="conga_36.png">
    <img id="conga_37" src="conga_37.png">
    <img id="conga_38" src="conga_38.png">
    <img id="conga_39" src="conga_39.png">
    <img id="conga_40" src="conga_40.png">
    <img id="conga_41" src="conga_41.png">
    <img id="conga_42" src="conga_42.png">
    <img id="conga_43" src="conga_43.png">
    <img id="conga_44" src="conga_44.png">
    <img id="conga_45" src="conga_45.png">
    <img id="conga_46" src="conga_46.png">
    <img id="conga_47" src="conga_47.png">
    <img id="conga_48" src="conga_48.png">
    <img id="conga_49" src="conga_49.png">
    <img id="conga_50" src="conga_50.png">
    <img id="conga_51" src="conga_51.png">
    <img id="conga_52" src="conga_52.png">
    <img id="conga_53" src="conga_53.png">
    <img id="conga_54" src="conga_54.png">
    <img id="conga_55" src="conga_55.png">
    <img id="conga_56" src="conga_56.png">
    <img id="conga_57" src="conga_57.png">
    <img id="conga_58" src="conga_58.png">
    <img id="conga_59" src="conga_59.png">
    <img id="conga_60" src="conga_60.png">
    <img id="conga_61" src="conga_61.png">
    <img id="conga_62" src="conga_62.png">
    <img id="conga_63" src="conga_63.png">
    <img id="conga_64" src="conga_64.png">
    <img id="conga_65" src="conga_65.png">
    <img id="conga_66" src="conga_66.png">
    <img id="conga_67" src="conga_67.png">
    <img id="conga_68" src="conga_68.png">
    <img id="conga_69" src="conga_69.png">
    <img id="conga_70" src="conga_70.png">
    <img id="conga_71" src="conga_71.png">
    <img id="conga_72" src="conga_72.png">
    <img id="conga_73" src="conga_73.png">
    <img id="conga_74" src="conga_74.png">
    <img id="conga_75" src="conga_75.png">
    <img id="conga_76" src="conga_76.png">
    <img id="conga_77" src="conga_77.png">
    <img id="conga_78" src="conga_78.png">
    <img id="conga_79" src="conga_79.png">
    <img id="conga_80" src="conga_80.png">
    <img id="conga_81" src="conga_81.png">
    <img id="conga_82" src="conga_82.png">
    <img id="conga_83" src="conga_83.png">
    <img id="conga_84" src="conga_84.png">
    <img id="conga_85" src="conga_85.png">
    <img id="conga_86" src="conga_86.png">
    <img id="conga_87" src="conga_87.png">
    <img id="conga_88" src="conga_88.png">
    <img id="conga_89" src="conga_89.png">
    <img id="conga_90" src="conga_90.png">
    <img id="conga_91" src="conga_91.png">
    <img id="conga_92" src="conga_92.png">
    <img id="conga_93" src="conga_93.png">
    <img id="conga_94" src="conga_94.png">
    <img id="conga_95" src="conga_95.png">
    <img id="conga_96" src="conga_96.png">
    <img id="conga_97" src="conga_97.png">
    <img id="conga_98" src="conga_98.png">
    <img id="conga_99" src="conga_99.png">
    <img id="conga_100" src="conga_100.png">
    <img id="conga_101" src="conga_101.png">
    <img id="conga_102" src="conga_102.png">
    <img id="conga_103" src="conga_103.png">
    <img id="conga_104" src="conga_104.png">
    <img id="conga_105" src="conga_105.png">
    <img id="conga_106" src="conga_106.png">
    <img id="conga_107" src="conga_107.png">
    <img id="conga_108" src="conga_108.png">
</div>

<div>
    <canvas id="testCanvas" width="1920" height="1080"></canvas>
</div>


<button id="add_dancer" onclick="addDancer();">add dancer</button>


<script src="jjcoords.js"></script>
<script id="editable">
    var canvas = document.getElementById("testCanvas");
    var stage = new createjs.Stage(canvas);
    var containers = [];
    var congaDancers = [];
    var counter = 0;

    function init() {

        var stageImg = new createjs.Bitmap(document.getElementById('stage').src);
        stage.addChild(stageImg);

        buildSprite();

        var curtains = new createjs.Bitmap(document.getElementById('curtains').src);
        stage.addChild(curtains);

        createjs.Ticker.addEventListener("tick", tick);

    }


    function buildSprite() {

        var container = createFrame('ryan3.png', document.getElementById(coords[counter].id).src, coords[counter].x, coords[counter].y, coords[counter].scale, coords[counter].rotation);
        containers.push(container);


    }

    function createFrame(consumerImg, dancerFrame, x, y, scale, rot) {

        var bitmap;
        var container = new createjs.Container();
        var consumerFace = new Image();
        consumerFace.src = consumerImg;
        consumerFace.onload = function () {


            var dancer = new createjs.Bitmap(dancerFrame);
            dancer.scaleX = 1.5;
            dancer.scaleY = 1.5;
            dancer.regX = dancer.image.width / 2 | 0;
            dancer.regY = dancer.image.height / 2 | 0;
            dancer.x = 100;
            dancer.y = 100;

            bitmap = new createjs.Bitmap(consumerFace.src);

            bitmap.regX = bitmap.image.width / 2 | 0;
            bitmap.regY = bitmap.image.height / 2 | 0;
            bitmap.scaleX = scale;
            bitmap.scaleY = scale;
            bitmap.x = x;
            bitmap.y = y;
            bitmap.rotation = rot;
//            bitmap.alpha = .5;

            container.name = 'ryan' + counter;
            container.addChild(dancer, bitmap);
            container.x = 700;
            container.y = 250;
            counter++;
            if (counter < coords.length) {
                buildSprite();

            } else {
                spriteBuilder();
            }


        };

        return container;

    }


    function spriteBuilder() {

        var square = new createjs.Container();

//        stage.addChild(containers[107]);
//        containers[107].y = 500;
        // create the sprite sheet builder:
        var builder = new createjs.SpriteSheetBuilder();

        var frames = [];
        for (var j = 0; j < containers.length; j++) {

            index = builder.addFrame(containers[j], null, 1, function (target, data) {

            }, j);
            // save off the index of each frame in order to use when defining the animation:

            frames.push(index);
        }

        // create an animation named square that comprises all of the frames we just added:
        // we're also telling it to loop the animation and setting a frequency so it updates every 8 ticks:

        builder.addAnimation("square", frames, true, 1);


        // run the build operation, and grab the resulting sprite sheet:
        // we could also do this asynchronously with buildAsync(...)
        var spriteSheet = builder.build();

        var square2 = new createjs.Sprite(spriteSheet, "square");
        square2.scaleX = .6;
        square2.scaleY = .6;
        square2.x = 250;
        square2.y = 620;
        stage.addChild(square2);

        // add in the generated spritesheet image for demo purposes:
//            stage.addChild(new createjs.Bitmap(spriteSheet._images[0])).set({x: 75, y: 150});
        // we want to do some work before we update the canvas,
        // otherwise we could use Ticker.addEventListener("tick", stage);
        congaDancers.push(square2);
        console.log(congaDancers);

    }

    var xpos = 200;
    function addDancer() {
        counter = 0;
        containers = [];
        buildSprite();

        xpos = xpos + 200;
        congaDancers[congaDancers.length - 1].x = xpos;

    }


    function tick() {

        stage.update(event);
    }


    init();


</script>
</body>
</html>

Solution

  • This is actually pretty simple. By caching 100+ images for each dancer, you are generating a TON of images/canvases in memory. This is 75,000,000 pixels for each dancer.

    SpriteSheets are a good way to cache content that is complex. However, drawing and swapping this many large textures may have a detrimental effect, instead of offering any benefits.

    There are lots of better ways to do this.

    1. If you really want to use SpriteSheets, then make one, and add the custom face over top. So create a container that has the dancer body, and a face sprite/bitmap. You can make as many of these as you like, and not generate a new set of giant images for each one.

    2. Rather than make a new SpriteSheet using SpriteSheetBuilder, just generate a definition that uses all your images you are already creating. The SpriteSheet supports multiple images, so you should be able to point each frame to a new image. Then take the same approach as earlier

    3. Where did this art come from? If you are animating using body part images, consider doing that in EaselJS instead of saving bitmaps. A bunch of small body parts reused will be way more performant.

    4. If you started with Vectors, consider using Adobe Animate to export them. This will generate you scalable vector content that you can use instead. Granted, a whole ton of vectors on canvas is not ideal, but it will not be taxing your memory the way you are now.

    I would also consider preloading the images to create a better experience. Hope this helps!