(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));