6
\$\begingroup\$

I just wanted to gather your opinion on my little JavaScript library. It features Math, String, Array, canvas and location extensions, as well as some useful animation and timing classes I came up with:

/* ====================== */
/* ======= Math ========= */
/* ====================== */

// Random number
Math.rand = function(min, max, float) {
    var random = Math.random() * (max-min) + min;
    return float ? random : Math.round(random);
};

// Random number with seed (not fully functional)
Math.srand = function(seed, min, max, float) {
    var code = 0;
    for (var i = 0, l = seed.length; i < l; i++) { code += seed.charCodeAt(i); }
    while (code > 1) { code/= 10; }

    var mod = code;
    var MAX_INT = Math.pow(2,32) - 1;
    var min = min != null ? min : -MAX_INT;
    var max = max != null ? max : MAX_INT;
    var random = mod * (max-min) + min;

    return float ? random : Math.round(random);
};

// 3.141592653589793 -> 180°
Math.toDegrees = function(angle) {
    return angle * (180 / Math.PI);
};

// 180° -> 3.141592653589793
Math.toRadians = function(angle) {
    return angle * (Math.PI / 180);
};


/* ====================== */
/* ======= String ======= */
/* ====================== */

// string -> String
String.prototype.capitalize = function() {
    return this.charAt(0).toUpperCase() + this.slice(1);
};

// "test".charAt(2, "oa") -> "toast"
String.prototype.charAt = function(index, replacement) {
    if (replacement == null) { return this.substr(index, 1); }
    else { return this.substr(0, index-1) + replacement + this.substr(index); }
};


/* ====================== */
/* ======= Array ======== */
/* ====================== */

// Compares values to an array object and returns the index
Array.prototype.compare = function(values) {
    for (var i = 0, l = this.length; i < l; i++) {
        var matches = [];

        for (var ii = 0, ll = values.length; ii < ll; ii++) {

            if (this[i][values[ii][0]] == values[ii][1]) { matches.push(true); }
        }

        if (matches.length == values.length) { return i; }
    }
};

// Reaplces values in a numeric array
Array.prototype.replace = function(a, b) {
    for (var i = 0, l = this.length; i < l; i++) {
        if (this[i] == a) { this[i] = b; }
    }
};


/* ====================== */
/* ======= Animation ==== */
/* ====================== */

// An advanced interval class which also calculates delta and fps
var Loop = function(callback, fps) {

    var _this = this;

    this.fps = fps ? fps : 60;
    this.real_fps = null;

    this.delta = 1;
    this.lastTime = +new Date;

    this.request = null;
    this.callback = callback;

    this.setFPS = function(fps) { this.fps = fps; };

    this.start = function() {

        // Timeout is used to specify a framerate
        this.request = setTimeout(function() {
            requestAnimFrame(function() {
                _this.start();
                _this.delta = (+new Date - _this.lastTime) / (1000 / _this.fps);
                _this.real_fps = Math.round((1 + (1-_this.delta))*_this.fps);
                _this.callback();
                _this.lastTime = +new Date;
            });
        }, 1000 / this.fps);
    };

    this.stop = function() {
        clearTimeout(this.request);
    };
};

// An advanced timeout class which also calculates the current progress
var Ticker = function(start, duration, callback) {

    var _this = this;

    this.start = start;
    this.duration = duration;

    // TODO easing options

    this.reverse = false;
    this.auto_reset = false;
    this.auto_reverse = false;

    this.callback = callback ? callback : null;
    this.timeout = callback ? setTimeout(function() {
        _this.callback();
        if (_this.auto_reset || _this.auto_reverse) { _this.reset(); }
    }, this.duration) : null;

    this.status = function() {

        var end = this.start + this.duration;
        var current = +new Date;
        var duration = end - this.start;
        var difference = current - this.start;
        var progress = this.reverse ? 1 - (difference / duration) : (difference / duration);

        if (current >= end) { progress = this.reverse ? 0 : 1; }

        return progress;
    };

    this.reset = function() {
        if (this.auto_reverse) { this.reverse = this.reverse ? false : true; }

        this.timeout = this.callback ? setTimeout(function() {
            _this.callback();
            if (_this.auto_reset || _this.auto_reverse) { _this.reset(); }
        }, this.duration) : null;

        this.start = +new Date;
    }; 
};

// Unifies the HTML5 requestAnimationFrame function
window.requestAnimFrame = (function() {
    return  window.requestAnimationFrame       || 
            window.webkitRequestAnimationFrame || 
            window.mozRequestAnimationFrame    || 
            window.oRequestAnimationFrame      || 
            window.msRequestAnimationFrame     || 
            function(callback) { window.setTimeout(callback, 1000 / 60); };
})();


/* ====================== */
/* ======= Canvas ======= */
/* ====================== */

CanvasRenderingContext2D.prototype.center = function() {
    return {
        x: Math.round(this.canvas.width / 2),
        y: Math.round(this.canvas.height / 2)
    };
};

// Extends the canvas fillRect function to be able to draw round corners
CanvasRenderingContext2D.prototype.fillRect = function(x, y, w, h, r1, r2, r3, r4) {
    if (r1 == null) {
        this.beginPath();
        this.moveTo(x, y);
        this.lineTo(x + w, y);
        this.lineTo(x + w, y + h);
        this.lineTo(x, y + h);
        this.closePath();
        this.fill();
    } else {

        var r2 = r2 == null ? r1 : r2;
        var r3 = r3 == null ? r1 : r3;
        var r4 = r4 == null ? r2 : r4;
        r4 = r4 == null ? r1 : r4;

        this.beginPath();
        this.moveTo(x, y + r1);
        this.quadraticCurveTo(x, y, x + r1, y);
        this.lineTo(x + w - r2, y);
        this.quadraticCurveTo(x + w, y, x + w, y + r2);
        this.lineTo(x + w, y + h - r3);
        this.quadraticCurveTo(x + w, y + h, x + w - r3, y + h);
        this.lineTo(x + r4, y + h);
        this.quadraticCurveTo(x, y + h, x, y + h - r4);
        this.closePath();
        this.fill();
    }
};

// Extends the canvas strokeRect function to be able to draw round corners
CanvasRenderingContext2D.prototype.strokeRect = function(x, y, w, h, r1, r2, r3, r4) {
    if (r1 == null) {
        this.beginPath();
        this.moveTo(x, y);
        this.lineTo(x + w, y);
        this.lineTo(x + w, y + h);
        this.lineTo(x, y + h);
        this.closePath();
        this.stroke();
    } else {

        var r2 = r2 == null ? r1 : r2;
        var r3 = r3 == null ? r1 : r3;
        var r4 = r4 == null ? r2 : r4;
        r4 = r4 == null ? r1 : r4;

        this.beginPath();
        this.moveTo(x, y + r1);
        this.quadraticCurveTo(x, y, x + r1, y);
        this.lineTo(x + w - r2, y);
        this.quadraticCurveTo(x + w, y, x + w, y + r2);
        this.lineTo(x + w, y + h - r3);
        this.quadraticCurveTo(x + w, y + h, x + w - r3, y + h);
        this.lineTo(x + r4, y + h);
        this.quadraticCurveTo(x, y + h, x, y + h - r4);
        this.closePath();
        this.stroke();
    }
};


/* ====================== */
/* ======= Document ===== */
/* ====================== */

// Simple cookie handling object
var Cookie = {
    set: function(name, value, days) {
        if (days) {
            var date = new Date();
            date.setTime(date.getTime()+(days*24*60*60*1000));
            var expires = "; expires="+date.toGMTString();
        }
        else var expires = "";
        document.cookie = name + "=" + value + expires + "; path=" + location.pathname;
    },

    unset: function(name) { Cookie.set(name, "", -1); },

    get: function(name) {
        var search = new RegExp(name + "=([^;]*);");
        var result = search.exec(document.cookie);
        return result ? result[1] : null;
    }
};

// Returns the value of the specified GET parameter (equivalent to PHP's $_GET[name])
location.get = function(name) {
    var search = new RegExp(encodeURIComponent(name) + "=([^&]*)&?");
    var result = search.exec(location.search);
    return result ? result[1] : null;
};

function include(file) {
    var dir = document.getElementsByTagName("script")[0].getAttribute("src").match(/([\w_-]+)\/[\w_-]+\.js/)[1];
    var script = document.createElement("script");
    script.setAttribute("src", dir + "/" + file);
    document.getElementsByTagName("head")[0].appendChild(script);
}

function createRequestObject() {

    var request, browser = navigator.appName;

    if (browser == "Microsoft Internet Explorer")
    { request = new ActiveXObject("Microsoft.XMLHTTP"); }
    else
    { request = new XMLHttpRequest(); }

    return request;
}
\$\endgroup\$
6
\$\begingroup\$

Here are some tips.

1)

Avoid using java keywords. In Math.rand, there is a variable called float. Try calling it toFloat.

2)

It's best not to browser sniff. Detect for features instead.

Old Code:

function createRequestObject() {

    var request, browser = navigator.appName;

    if (browser == "Microsoft Internet Explorer")
    { request = new ActiveXObject("Microsoft.XMLHTTP"); }
    else
    { request = new XMLHttpRequest(); }

    return request;
}

New Code:

function createRequestObject() {
    var request;
    if (ActiveXObject) {
        request = new ActiveXObject("Microsoft.XMLHTTP");
    } else {
        request = new XMLHttpRequest();
    }
    return request;
}

3)

Try not to repeat yourself.

Old Code:

CanvasRenderingContext2D.prototype.strokeRect = function(x, y, w, h, r1, r2, r3, r4) {
    if (r1 == null) {    
        this.beginPath();
        //...
        this.closePath();
        this.stroke();
    } else {
        //...
        this.beginPath();
        //...
        this.closePath();
        this.stroke();
    }
};

New Code:

CanvasRenderingContext2D.prototype.strokeRect = function(x, y, w, h, r1, r2, r3, r4) {
    this.beginPath();
    if (r1 == null) {    
        this.moveTo(x, y);
        //...
    } else {
        //...
    }
    this.closePath();
    this.stroke();
};

4)

Make functions as small as possible. Try adding functions to the prototype instead of creating them inside the constructor.

Old Code:

var Loop = function(callback, fps) {
// ...
  this.start = function() {

        // Timeout is used to specify a framerate
        this.request = setTimeout(function() {
            requestAnimFrame(function() {
                _this.start();
                _this.delta = (+new Date - _this.lastTime) / (1000 / _this.fps);
                _this.real_fps = Math.round((1 + (1-_this.delta))*_this.fps);
                _this.callback();
                _this.lastTime = +new Date;
            });
        }, 1000 / this.fps);
    };

//...
};

New Code:

Loop.prototype.start = function () {
    // Timeout is used to specify a framerate
            var _this = this;
    this.request = setTimeout(function () {
            requestAnimFrame(function () {
                _this.start();
                _this.delta = (+new Date() - _this.lastTime) / (1000 / _this.fps);
                _this.real_fps = Math.round((1 + (1 - _this.delta)) * _this.fps);
                _this.callback();
                _this.lastTime = +new Date();
            });
        }, 1000 / this.fps);
};

5)

Rename String.prototype.charAt to String.prototype.replaceByIndex

6)

this.fps = fps ? fps : 60; is the same as this.fps = fps || 60;

\$\endgroup\$
  • \$\begingroup\$ Thanks a bunch! One question though: In the prototype example, how do I create the necessary reference _this used to have ? \$\endgroup\$ – asdf Sep 11 '12 at 9:12
  • \$\begingroup\$ @elias94xx Create a reference outside the function to this called _this. Refer to the updated code example. \$\endgroup\$ – Larry Battle Sep 11 '12 at 14:36
  • \$\begingroup\$ Oh of course, got confused a little. :) \$\endgroup\$ – asdf Sep 11 '12 at 14:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.