JavaScript Tween function

This is a small library I wrote for creating simple and complicated easing effects without using JQuery, MooTools or other JS libraries.

The library is small 13.4 KB or 5.6 KB compressed,

The function can be called as follows:

tween(element, property, from, to, duration, [optional] function)

Parameters

element: The idĀ  of the HTML element you want to animate likeĀ  “my_div”, “left_sidebar”, DOM objects are also accepted

property: The CSS property that you want to animate like “color”, background-color”, “left”, “margin-top”…

from: The initial value, like 0px, top, none, 0.5…

to: The final value

duration: the animation duration in ms

function (optional): the easing function to use, by default the tween function uses the linear tween, there are nine other functions included (easeInBack, easeOutBack, easeInOutBack, easeInCirc, easeOutCirc, easeInOutCirc, easeInBounce, easeOutBounce & easeInOutBounce)

Examples

Demos available here

Minified version

Function available here

Full version

var fps = 40;
var debugging = 0;
var tw = [];
var rand_str = generatePassword();
var float_pcre = new RegExp("(\+|-)?(\d*\.)?\d+", 'g');
var color_at_end = new RegExp("[Cc]olor$");

function tween(element, property, from, to, duration) {

    if (arguments.length = 0.75) {
        for (var i = 0; i < tw.length; ++i) {
            if (tw[i]["status"] === "playing") break;
            if (i == tw.length - 1) {
                tw = [];
            }
        }
    }

    var tw_id = tw.length;
    tw[tw_id] = [];
    tw[tw_id]["f"] = (Boolean(arguments[5])) ? arguments[5] : "linear";
    tw[tw_id]["ez"] = 0; // defined here to avoid collisions on simultaneous tw
    tw[tw_id]["status"] = "playing"; // playing, aborted, complete
    tw[tw_id]["element"] = resolve_element(element);
    tw[tw_id]["property"] = resolve_property(property);
    if (Boolean(tw[tw_id]["property"].match(color_at_end))) tw[tw_id]["is_color_tween"] = true;
    tw[tw_id]["duration"] = duration; //ms
    tw[tw_id]["step"] = 0;
    tw[tw_id]["interval"] = 1000 / fps; //ms
    tw[tw_id]["steps"] = Math.ceil(duration / tw[tw_id]["interval"]);
    tw[tw_id]["from_orig"] = from; //  '', 'current', '1', '30%' ...
    tw[tw_id]["to_orig"] = to;
    tw[tw_id]["timer"] = false;

    // Stop simultaneous
    for (var kounter = 0; kounter < tw_id; ++kounter) {
        if (tw[kounter]["status"] !== "playing") continue;
        if (tw[kounter]["element"] !== tw[tw_id]["element"]) continue;
        if (tw[kounter]["property"] !== tw[tw_id]["property"]) continue;
        // We arrived here! that means 2 tw on the same element with the same property, keep the newest tween..
        tw[kounter]["status"] = "aborted";
        clearInterval(tw[kounter]["timer"]);
    }


    if (tw[tw_id]["from_orig"] === 'current') {
        tw[tw_id]["from"] = getStyle(tw[tw_id]["element"], tw[tw_id]["property"]);
        if (tw[tw_id]["from"] === '' || tw[tw_id]["from"] === 'NaN' || typeof (tw[tw_id]["from"]) === "undefined") tw[tw_id]["from"] = tw[tw_id]["element"].style[tw[tw_id]["property"]];
        if (tw[tw_id]["from"] === '' || tw[tw_id]["from"] === 'NaN' || typeof (tw[tw_id]["from"]) === "undefined") {
            if (tw[tw_id]["is_color_tween"]) tw[tw_id]["from"] = "#7F7F7F";
            else tw[tw_id]["from"] = '0';
        }
        // Format the color array
        if (tw[tw_id]["is_color_tween"]) tw[tw_id]["from"] = resolve_color(tw[tw_id]["from"]);
    }


    if (tw[tw_id]["is_color_tween"]) {
        if (tw[tw_id]["from_orig"] === 'current'); // tw[tw_id]["from"]	= tw[tw_id]["from"];
        else tw[tw_id]["from"] = resolve_color(tw[tw_id]["from_orig"]);
        tw[tw_id]["to"] = resolve_color(tw[tw_id]["to_orig"]);
        tw[tw_id]["change"] = [];
        tw[tw_id]["change"]["red"] = tw[tw_id]["to"]["red"] - tw[tw_id]["from"]["red"];
        tw[tw_id]["change"]["green"] = tw[tw_id]["to"]["green"] - tw[tw_id]["from"]["green"];
        tw[tw_id]["change"]["blue"] = tw[tw_id]["to"]["blue"] - tw[tw_id]["from"]["blue"];
    } else {
        if (tw[tw_id]["from_orig"] !== '') {
            // tmp is a temporary variable that I use for retrieving prefix and suffix
            if (tw[tw_id]["from_orig"] === 'current') {
                try {
                    tw[tw_id]["from"] = tw[tw_id]["from"].match(float_pcre)
                } catch (e) {
                    tw[tw_id]["from"] = 0;
                }
            } else tw[tw_id]["from"] = tw[tw_id]["from_orig"].match(float_pcre);

            tw[tw_id]["from"] = parseFloat(tw[tw_id]["from"]);
            tw[tw_id]["to"] = parseFloat(tw[tw_id]["to_orig"].match(float_pcre));
            tw[tw_id]["change"] = tw[tw_id]["to"] - tw[tw_id]["from"];

            var tmp;
            if (tw[tw_id]["from_orig"] === 'current') tmp = tw[tw_id]["to_orig"];
            else tmp = tw[tw_id]["from_orig"];
            tmp = tmp.replace(float_pcre, rand_str);
            tw[tw_id]["prefix"] = tmp.slice(0, tmp.indexOf(rand_str));
            tw[tw_id]["suffix"] = str_replace(tw[tw_id]["prefix"] + rand_str, '', tmp);
        }
    }

    // debuggg...
    if (debugging) {
        var tw_info = new String();
        var elm_props = ['id', 'tagName', 'className'];
        for (var i in tw[tw_id]) {
            tw_info += i + ": ";
            if (i !== "element" && typeof (tw[tw_id][i]) === typeof (Object())) {
                for (var j in tw[tw_id][i])
                tw_info += "nt" + j + ": " + tw[tw_id][i][j];
            } else if (i === "element") {
                for (var j = 0; j < elm_props.length; j++)
                tw_info += "nt" + elm_props[j] + ": " + tw[tw_id][i][elm_props[j]];
            } else tw_info += tw[tw_id][i];
            tw_info += "nn";
        }
        alert(tw_info);
    }

    if (tw[tw_id]["from_orig"] === "") {
        tw[tw_id]["status"] = "playing";
        tw[tw_id]["timer"] = setTimeout("tw[" + tw_id + "]["element"].style[tw[" + tw_id + "]["property"]] = tw[" + tw_id + "]["to_orig"];tw[" + tw_id + "]["status"] = "complete"", tw[tw_id]["duration"]);
    } else ease(tw_id);

    return tw_id;
}

function ease(tw_id) {

    if (tw[tw_id]["step"] === 0) {
        tw[tw_id]["status"] = "playing";
        if (tw[tw_id]["is_color_tween"]) tw[tw_id]["element"].style[tw[tw_id]["property"]] = "rgb(" + tw[tw_id]["from"]["red"] + "," + tw[tw_id]["from"]["green"] + "," + tw[tw_id]["from"]["blue"] + ")";
        else tw[tw_id]["element"].style[tw[tw_id]["property"]] = tw[tw_id]["prefix"] + tw[tw_id]["from"] + tw[tw_id]["suffix"];
    }

    tw[tw_id]["step"]++;

    if (tw[tw_id]["step"] = tw[tw_id]["steps"]) {
        tw[tw_id]["status"] = "complete";
    }

}

function ease_repeat(tw_id) {
    try {
        tw[tw_id]["element"].style[tw[tw_id]["property"]] = tw[tw_id]["ez"];
        ease(tw_id);
    } catch (e) {}
}


/* Keep it simple with these nice functions */

function h2d(h) {
    // hex to decimal
    return parseInt(h, 16);
}

function str_replace(search, replace, subject, count) {
    // version: 908.406
    // discuss at: http://phpjs.org/functions/str_replace
    var i = 0,
        j = 0,
        temp = '',
        repl = '',
        sl = 0,
        fl = 0,
        f = [].concat(search),
        r = [].concat(replace),
        s = subject,
        ra = r instanceof Array,
        sa = s instanceof Array;
    s = [].concat(s);
    if (count) this.window[count] = 0;
    for (i = 0, sl = s.length; i < sl; i++) {
        if (s[i] === '') continue;
        for (j = 0, fl = f.length; j  "MozBorderRadius";
    var matched;
    var dash_letter = new RegExp("-[a-z]");
    while (1) {
        if (matched = property.match(dash_letter)) {
            matched = matched.toString();
            property = str_replace(matched, matched.charAt(1).toUpperCase(), property);
        } else break;
    }
    return property;
}

function resolve_color(color) {
    // return an array containing R, G and B values 
    if (color === 'transparent') // IE (6 and ?)
    color = '#FFF';
    var r, g, b;
    var hex_color_pcre = new RegExp("^#[0-9a-f]{3}([0-9a-f]{3})?$", 'gi');
    var rgb_color_pcre = new RegExp("rgb\(\s*((?:[0-2]?[0-9])?[0-9])\s*,\s*((?:[0-2]?[0-9])?[0-9])\s*,\s*((?:[0-2]?[0-9])?[0-9])\s*\)$", 'gi');
    var rgb_percent_color_pcre = new RegExp("rgb\(\s*((?:[0-1]?[0-9])?[0-9])%\s*,\s*((?:[0-1]?[0-9])?[0-9])%\s*,\s*((?:[0-1]?[0-9])?[0-9])%\s*\)$", 'gi');
    if (color.match(hex_color_pcre)) {
        if (color.length == 4) {
            r = color.charAt(1) + "" + color.charAt(1);
            g = color.charAt(2) + "" + color.charAt(2);
            b = color.charAt(3) + "" + color.charAt(3);
        } else {
            r = color.charAt(1) + "" + color.charAt(2);
            g = color.charAt(3) + "" + color.charAt(4);
            b = color.charAt(5) + "" + color.charAt(6);
        }
        r = h2d(r);
        g = h2d(g);
        b = h2d(b);
    } else if (color.match(rgb_color_pcre)) {
        r = RegExp.$1;
        g = RegExp.$2;
        b = RegExp.$3;
    } else if (color.match(rgb_percent_color_pcre)) {
        r = parseInt((RegExp.$1) * 2.55);
        g = parseInt((RegExp.$2) * 2.55);
        b = parseInt((RegExp.$3) * 2.55);
    } else return false;

    var returned = [];
    returned['red'] = r;
    returned['green'] = g;
    returned['blue'] = b;
    return returned;
}

function getStyle(oElm, strCssRule) {
    // http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/
    var strValue = "";
    if (document.defaultView && document.defaultView.getComputedStyle) {
        strValue = document.defaultView.getComputedStyle(oElm, "").getPropertyValue(strCssRule);
    } else if (oElm.currentStyle) {
        strCssRule = strCssRule.replace(/-(w)/g, function (strMatch, p1) {
            return p1.toUpperCase();
        });
        strValue = oElm.currentStyle[strCssRule];
    }
    return strValue;
}

function generatePassword(len) {
    // http://kadimi.com/en/javascript/random-password-string/
    len = parseInt(len);
    if (!len) len = 6;
    var password = "";
    var chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    var charsN = chars.length;
    var nextChar;

    for (i = 0; i < len; i++) {
        nextChar = chars.charAt(Math.floor(Math.random() * charsN));
        password += nextChar
    }
    return password;
}

function substr(f_string, f_start, f_length) {
    // version: 908.406
    // discuss at: http://phpjs.org/functions/substr
    f_string += '';

    if (f_start < 0) {
        f_start += f_string.length;
    }

    if (f_length == undefined) {
        f_length = f_string.length;
    } else if (f_length < 0) {
        f_length += f_string.length;
    } else {
        f_length += f_start;
    }

    if (f_length  1000000000 && timeStamp < 2000000000
    var now = new Date().getTime() / 1000;
    var s = parseInt(now, 10);

    return (get_as_float) ? now : (Math.round((now - s) * 1000) / 1000) + ' ' + s;
}

// My favorite $()


function $() {
    //http://v3.thewatchmakerproject.com/code/extended-dollar.txt
    var elements = new Array();
    for (var i = 0, len = arguments.length; i < len; i++) {
        var element = arguments[i];
        if (typeof element == 'string') {
            var matched = document.getElementById(element);
            if (matched) {
                elements.push(matched);
            } else {
                var allels = (document.all) ? document.all : document.getElementsByTagName('*');
                var regexp = new RegExp('(^| )' + element + '( |$)');
                for (var i = 0, len = allels.length; i < len; i++) if (regexp.test(allels[i].className)) elements.push(allels[i]);
            }
            if (!elements.length) elements = document.getElementsByTagName(element);
            if (!elements.length) {
                elements = new Array();
                var allels = (document.all) ? document.all : document.getElementsByTagName('*');
                for (var i = 0, len = allels.length; i < len; i++) if (allels[i].getAttribute(element)) elements.push(allels[i]);
            }
            if (!elements.length) {
                var allels = (document.all) ? document.all : document.getElementsByTagName('*');
                for (var i = 0, len = allels.length; i < len; i++) if (allels[i].attributes) for (var j = 0, lenn = allels[i].attributes.length; j < lenn; j++) if (allels[i].attributes[j].specified) if (allels[i].attributes[j].nodeValue == element) elements.push(allels[i]);
            }
        } else {
            elements.push(element);
        }
    }
    if (elements.length == 1) {
        return elements[0];
    } else {
        return elements;
    }
}


// Ease function(s)
linear = function (t, b, c, d) {
    return c * t / d + b;
};




















// ease functions
// Back
easeInBack = function (t, b, c, d, s) {
    if (s == undefined) s = 1.70158;
    return c * (t /= d) * t * ((s + 1) * t - s) + b;
};
easeOutBack = function (t, b, c, d, s) {
    if (s == undefined) s = 1.70158;
    return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
};
easeInOutBack = function (t, b, c, d, s) {
    if (s == undefined) s = 1.70158;
    if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
    return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
};





//Circulat
easeInCirc = function (t, b, c, d) {
    return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
};

easeOutCirc = function (t, b, c, d) {
    return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
};

easeInOutCirc = function (t, b, c, d) {
    if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
    return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
};

//Bounce
easeInBounce = function (t, b, c, d) {
    return c - easeOutBounce(d - t, 0, c, d) + b;
};

easeOutBounce = function (t, b, c, d) {
    if ((t /= d) < (1 / 2.75)) {
        return c * (7.5625 * t * t) + b;
    } else if (t < (2 / 2.75)) {
        return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b;
    } else if (t < (2.5 / 2.75)) {
        return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b;
    } else {
        return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b;
    }
};
easeInOutBounce = function (t, b, c, d) {
    if (t < d / 2) return easeInBounce(t * 2, 0, c, d) * .5 + b;
    return easeOutBounce(t * 2 - d, 0, c, d) * .5 + c * .5 + b;
};
//*/

4 thoughts on “JavaScript Tween function

Thanks Kadimi for sharing this tween() javascript function:

I have taken the original ‘minimized’ tween() js code and expanded it back to a human readable source. I have corrected some JSLint issues presented with code and additionally corrected two js ‘if conditionals’ that had no ‘TRUE’ state code to execute. I think this code you have created is educational in human readable code format:


// Use:   tween(element, property, from, to, duration, [optional] function)
// Where: element: ID of HTML element, property: CSS property for tween'ing, from: initial value, to: final value

var fps = 40;
var debugging = 0;
var tw = [];
[...] code removed by Nabil Kadimi

Peter Bowey

Thank you for your interest, this small library is a draft and I will put more effort on improving if I see that people like you are willing to support it by providing bug reports and feedback.

I posted the full version of the function and I will – in the near future – have a repository and a bug tracker.

Notes: The two (original) failed if conditionals occur on the following code:

1) if (tw[f]["from_orig"] === 'current'); // line 53
2) if (typeof(a) == "object"); // line 155

There is no TRUE state code to execute after either of the above conditions. Note the semicolon ; ending each condition

Peter Bowey

I wrote that on purpose, this doesn’t hurt but sure there is a better way to write it.

Actually I should have wrote a comment to remind me why I coded it that way… oops.

And y the way, I forgot to tell you that I was very busy with my personal life, I apologize for not responding to your comments earlier.

Leave a Reply

Your email address will not be published. Required fields are marked *