JohnMalkowich
JohnMalkowich

Reputation: 298

Looking for thoughts on improvement of my javascript (jquery) code. Recursive function

I have made this code that makes some visual "tiles" that fades in and out. But at the moment I'm having a little performance problem.

Though most browers are running the code okay (especially firefox), some like safari have problems after a while (a while = like 15 seconds).

I think its due to my recursive function (the function named changeopacity that calls itself forever on a delay)? or is it?

But anyways the problem is that this code is really heavy for most browsers. Is there, or more how can I make this code perform any better? any thoughts? (code examples would be nice) thanks :-)

The actual code:

$(document).ready(function () {

    var aniduration = 2000;
    var tilesize = 40;

    createtable(tilesize);



    $(".tile").each(function (index, domEle) {
        var randomdelay = Math.floor(Math.random() * 3000);
        setTimeout(function () {
            changeopacity(aniduration, domEle);
        }, randomdelay);
    });



    $("td").click(function () {
        clickanimation(this, 9);
    });

    $("td").mouseenter(function () {
        var element = $(this).find("div");
        $(element).clearQueue().stop();
        $(element).animate({opacity: "0.6"}, 800);
    });


    $("td").css("width", tilesize + "px").css("height", tilesize + "px");



});

function createtable(tilesize) {
    var winwidth = $(window).width();
    var winheight = $(window).height();
    var horztiles = winwidth / tilesize;
    var verttiles = winheight / tilesize;


for (var y = 0; y < verttiles; y++)
{
    var id = "y" + y;
    $("#tbl").append("<tr id='" + id + "'></tr>");

    for (var x = 0; x < horztiles; x++)
    {
        $("#" + id).append("<td><div class='tile' style='opacity: 0; width: " + tilesize + "px; height: " + tilesize + "px;'></div></td>");
    }
}   
}

function changeopacity(duration, element){
    var randomnum = Math.floor(Math.random() * 13);
    var randomopacity = Math.floor(Math.random() * 7);
    var randomdelay = Math.floor(Math.random() * 1000);

    if ($(element).css("opacity") < 0.3)
    {
        if (randomnum != 4)
        {
            if ($(element).css("opacity") != 0)
            animation(element, 0, duration, randomdelay);
        }
        else
        {
            animation(element, randomopacity, duration, randomdelay);
        }
    }
    else
    {
        animation(element, randomopacity, duration, randomdelay);
    }

    setTimeout(function () {
        return changeopacity(duration, element);
    }, duration + randomdelay);
}

function animation(element, randomopacity, duration, randomdelay){
    $(element).clearQueue().stop().delay(randomdelay).animate({opacity: "0." + randomopacity}, duration);
}


function clickanimation(column, opacitylevel) {
        var element = $(column).find("div");
        $(element).clearQueue().stop();
        $(element).animate({"background-color": "white"}, 200);
        $(element).animate({opacity: "0." + opacitylevel}, 200);
        $(element).delay(200).animate({opacity: "0.0"}, 500);
        //$(element).delay(600).animate({"background-color": "black"}, 500);
}

Upvotes: 3

Views: 225

Answers (2)

Bergi
Bergi

Reputation: 664385

Nice animation :-) However, I found some bugs and possible improvements:

  • Your table is not rebuilt on window resizes. Not sure if bug or feature :-)
  • Use delegated events. You have a lot of elements, and every event handler is costly. Sadly, this won't work for the non-bubbling mouseenter event.
  • It would be nice if you would not use inline styles for with and height - those don't change. For the divs, they are superflouos anyway.
  • I can't see a reason for all those elements to have ids. The html-string building might be more concise.

  • Cache the elements!!! You are using the jQuery constructor on nearly every variable, building a new instance. Just reuse them!

  • Your changeopacity function looks a bit odd. If the opacity is lower than 0.3, there is 1-in-13-chance to animate to zero? That might be expressed more stringent. You also might cache the opacity to a variable instead of reading it from the dom each time.

  • There is no reason to pass the duration and other constants as arguments, they do never change and can be used from the global scope.

  • Instead of using the timeout, you should use the complete callback of the animate method. Timeouts are never accurate, they may even interfere here causing (minor) problems.

var duration = 2000,
    tilesize = 40,
    clickopacity = 0.9;

$(document).ready(function () {

    filltable($("#tbl"), tilesize)
      .on("click", "td", clickanimation);

    $(".tile").each(function() {
        changeopacity($(this));
    });

    $("#tbl div").mouseenter(function () {
        $(this).clearQueue()
               .stop()
               .animate({opacity: "0.6"}, 800);
    });
});

function filltable(tbl, tilesize) {
    var win = $(window).width();
    var horztiles = win.width() / tilesize;
    var verttiles = win.height() / tilesize;

    for (var y = 0; y < verttiles; y++) {
        var tr = "<tr>";
        for (var x = 0; x < horztiles; x++)
            tr += "<td style='width:"+tilesize+"px;height:"+tilesize+"px;'><div class='tile' style='opacity:0;'></div></td>");
        tbl.append(tr+"</tr>");
    }
    return tbl;
}

function changeopacity(element) {
    var random = Math.floor(Math.random() * 13);
    var opacity = Math.floor(Math.random() * 7);
    var delay = Math.floor(Math.random() * 1000);

    if (element.css("opacity") < 0.3 && random != 4)
        opacity = 0;

    element.clearQueue().stop().delay(delay).animate({
        opacity: "0." + opacity
    }, duration, function() {
        changeopacity(element);
    });
}

function clickanimation() {
    $(this.firstChild)
      .clearQueue()
      .stop()
      .animate({"background-color": "white"}, 200)
      .animate({opacity: "0." + clickopacity}, 200)
      .delay(200).animate({opacity: "0.0"}, 500);
    //.delay(600)
    //.animate({"background-color": "black"}, 500);
}

Upvotes: 2

Niet the Dark Absol
Niet the Dark Absol

Reputation: 324620

The number one issue is that you are creating one setTimeout for every single cell on your page. The only browser capable of handling that is Internet Explorer, and then it fails due to the many CSS changes causing slow redraws.

I would strongly suggest programming your own event scheduler. Something like this, which I used in a university project:

var timer = {
    length: 0,
    stack: {},
    timer: null,
    id: 0,
    add: function(f,d) {
        timer.id++;
        timer.stack[timer.id] = {f: f, d: d, r: 0};
        timer.length++;
        if( timer.timer == null) timer.timer = setInterval(timer.run,50);
        return timer.id;
    },
    addInterval: function(f,d) {
        timer.id++;
        timer.stack[timer.id] = {f: f, d: d, r: d};
        timer.length++;
        if( timer.timer == null) timer.timer = setInterval(timer.run,50);
        return timer.id;
    },
    remove: function(id) {
        if( id && timer.stack[id]) {
            delete timer.stack[id];
            timer.length--;
            if( timer.length == 0) {
                clearInterval(timer.timer);
                timer.timer = null;
            }
        }
    },
    run: function() {
        var x;
        for( x in timer.stack) {
            if( !timer.stack.hasOwnProperty(x)) continue;
            timer.stack[x].d -= 50;
            if( timer.stack[x].d <= 0) {
                timer.stack[x].f();
                if( timer.stack[x]) {
                    if( timer.stack[x].r == 0)
                        timer.remove(x);
                    else
                        timer.stack[x].d = timer.stack[x].r;
                }
            }
        }
    }
};

Then, instead of using setTimeout, call timer.add with the same arguments. Similarly, instead of setInterval you can call timer.addInterval.

This will allow you to have as many timers as you like, and they will all run off a single setInterval, causing much less issues for the browser.

Upvotes: 2

Related Questions