(function($) { "use strict"; var letterfx = function(element, options) { this.options = $.extend({}, $.fn.letterfx.defaults, options); this.num_completed_fx = 0; this.is_done = false; this.monitor_timer = null; this.killswitch = null; this.$element = $(element); if (this.options.restore) this.original_html = this.$element.html(); this.init(); } letterfx.prototype.init = function() { this.new_html = this.$element.text().replace(this.options.pattern, this.options.replacement); this.$element.addclass(this.options.css.element.base).addclass(this.options.css.element.before); this.$element.html(this.new_html); this.$letters = this.$element.find(this.options.selector); this.$letters .css('transition-duration', this.options.fx_duration) .addclass(this.options.css.letters.base) .addclass(this.options.css.letters.before); this.bindletterfxend(); this.num_letters = this.$letters.length; this.fx(); return this; } letterfx.prototype.bindletterfxend = function() { var options = this.options; var lfx = this; this.$letters.bind("transitionend", function() { options.onlettercomplete($(this), lfx.$element, lfx); lfx.notifyfxend(); switch (options.letter_end) { case "destroy": $(this).remove(); break; case "rewind": lfx.applyletterfx($(this), options.timing, options.css.letters.after, options.css.letters.before); break; case "stay": break; // restore default: $(this).replacewith($(this).text()); } }); return lfx; } letterfx.prototype.terminate = function() { this.is_done = true; this.options.onelementcomplete(this.$element, this); cleartimeout(this.killswitch); switch (this.options.element_end) { case "destroy": this.$element.remove(); break; case "stay": break; // restore default: this.$element.html(this.original_html); this.$element.removeclass(this.options.css.element.base).removeclass(this.options.css.element.after); break; } } letterfx.prototype.notifyfxend = function() { cleartimeout(this.monitor_timer); this.num_completed_fx++; var lfx = this; this.monitor_timer = settimeout( function() { if (lfx.num_completed_fx % lfx.num_letters === 0) { lfx.terminate(); } }, math.max(this.options.timing + 10, 50) ); return this; } letterfx.prototype.startkillwatch = function() { var fx_duration = this.options.fx_duration.match(/\d+s/) ? parseint(this.options.fx_duration) : 1; var time = math.ceil(1.5 * this.num_letters * this.options.timing * fx_duration); var lfx = this; this.killswitch = window.settimeout(function() { if (!lfx.isdone()) { lfx.terminate() } }, time) } letterfx.prototype.fx = function() { var lfx = this; this.startkillwatch(); this.$element.removeclass(this.options.css.element.before).addclass(this.options.css.element.after); var $letters = this.options.sort(this.$letters); var options = this.options; $letters.each( function(i, letter) { lfx.applyletterfx($(letter), (i + 1) * options.timing, options.css.letters.before, options.css.letters.after); } ); return this; } letterfx.prototype.applyletterfx = function($letter, timing, css_before, css_after) { var options = this.options; window.settimeout( function() { $letter.removeclass(css_before).addclass(css_after); }, timing ); return this } letterfx.prototype.isdone = function() { return this.is_done; } var letterfxconfig = function(conf) { this.config = $.extend({}, $.fn.letterfx.defaults, conf); this.buildcss(this.config.backwards); // check & change to word pattern if (this.config.words) this.config.pattern = /(\s+)/g; } letterfxconfig.prototype.buildcss = function(flip) { var options = this.config; var before = flip ? 'after' : 'before'; var after = flip ? 'before' : 'after'; var css = { element: {}, letters: {} }; css.element.base = options.element_class + "-container " + options.fx.replace(/(\s+)/g, options.element_class + "-$1-container"); css.element[before] = options.fx.replace(/(\s+)/g, options.element_class + "-$1-before-container"); css.element[after] = options.fx.replace(/(\s+)/g, options.element_class + "-$1-after-container"); css.letters.base = options.element_class; css.letters[before] = options.fx.replace(/(\s+)/g, options.element_class + "-$1-before") css.letters[after] = options.fx.replace(/(\s+)/g, options.element_class + "-$1-after") this.config = $.extend(options, { 'css': css }); } letterfxconfig.prototype.getconfig = function() { return this.config; } letterfxconfig.parse = function(config) { return (new letterfxconfig(config)).getconfig(); } $.fn.letterfx = function(config) { config = letterfxconfig.parse(config); return $(this).each(function() { var $element = $(this); if (!$element.data('letterfx-obj') || $element.data('letterfx-obj').isdone()) { $element.data('letterfx-obj', new letterfx($element, config)); } }); }; $.fn.letterfx.sort = { random: function(array) { var currentindex = array.length, temporaryvalue, randomindex; // while there remain elements to shuffle... while (0 !== currentindex) { // pick a remaining element... randomindex = math.floor(math.random() * currentindex); currentindex -= 1; // and swap it with the current element. temporaryvalue = array[currentindex]; array[currentindex] = array[randomindex]; array[randomindex] = temporaryvalue; } return array; }, reverse: function($array) { return $array.toarray().reverse(); } } $.fn.letterfx.patterns = { letters: /(\s)/gi } // plugin configurables $.fn.letterfx.defaults = { // default fx fx: 'spin fly-top', // defaults to selecting all characters pattern: /(\s)/gi, // switch from letter fx to word fx. word: false, // fx, like fly or fade, can happen in (eg fade-in) or out (eg fade-out). // the default is in; change backwards to true to reverse the order of the effects. backwards: false, // defaults to injecting spans, can be any inline element replacement: "$1", //selector -- should match replacement above selector: 'span', // letter fx start sequentially: letters start their fx one after another. // this sets time between the letters timing: 50, //duration of each fx // options the same as css property for transition-duration ("1ms", "1s", etc) fx_duration: "1s", // stabile dimensions // stabilize:true, // sort callback for custom sorting elements sort: function($letters) { return $letters; }, // callback when letter is done animating. runs after each letter onlettercomplete: function($letter, $element, letterfxobj) {}, // runs after all are done. onelementcomplete: function($element, letterfxobj) {}, // what to do when a letter completes its animation. // options include // restore: return letter to plain text (default) // destroy: get rid of the letter. // stay: leave it as is. // rewind: reverse the animation letter_end: "restore", // what to do when the entire element has completed all its letter effects // options include: // restore: return element to its pre-fx state (default) // stay: do nothing // destroy: get rid of the element... forever element_end: "restore", // restore container element back to original state. // options: true, false, "element" ("element" waits until all letters complete fx before restoring) restore: true, // destroy element/letters after fx, useful on {out:true} fx // options: true, false, "letters" ("letters" destroys each letter after fx, but elements original content may be restored after all letters complete fx) destroy: false, // default class for injected elements element_class: 'letterfx', // placeholder values that are calculated on launch css: { element: { base: '', before: '', after: '' }, letters: { base: '', before: '', after: '' } } }; }(jquery));