function FancyText(config) { var defaults = { $container: $('.text'), // container holding text to animate transitionTime: 2.5, // time between each transition animationSpeed: 2, // time for words/letters to animate into place animationType: 'fade', // explode, fade splitBy: 'letter' // letter, word }; this.config = $.extend({}, defaults, config); this.charCache = []; this.indices = {}; this.debug = true; this.init(); } FancyText.prototype.init = function() { var self = this; self.injectText(self.config.$container); self.findMatches(); if (self.debug) { setTimeout(function() { self.doAnimation(); }, self.config.transitionTime * 1000); } else { self.doAnimation(); }; } FancyText.prototype.injectText = function(arr) { var self = this; var arr = []; var splitLetter = self.config.splitBy === 'letter' ? true : false; /* after is used to determine what to split characters by and whether or not to add a space after the closing span that wraps each character. */ var after = splitLetter ? '' : ' '; self.config.$container.each(function(i){ var $this = $(this); var inject = ''; arr = $this.text().split(after); self.charCache[i] = arr; // wraps span around each letter/word _.each(arr, function(item, i){ inject += '' + item + '' + after; }); $this.html(inject); self._findOffset($this); }); } FancyText.prototype._findOffset = function($el) { // add x/y coordinates to each letter $el.find('.char').each(function(i, item){ var $this = $(this); var position = $this.position(); // sets x/y position to variable before // being set to position absolute $this.css({ top : position.top, left : position.left }) }); // absolutely positions each letter $('.char').addClass('float'); } FancyText.prototype._rdmInt = function(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } FancyText.prototype.findMatches = function() { var self = this; /* TO DO: If multiple words/letter exist, intersection only pics one. So if one tweet shares 4 words with the next one only one of the words will animate - find something better than _.inersection. Also this all feels a little non-performant. There's got to be a cleaner way to organize all this. */ // finds matching words/letters between both tweets var matches = _.intersection(self.charCache[0], self.charCache[1]); var oldIndex = []; var newIndex = []; var indices = {}; /* Stores index of each matching word or letter. */ _.each(matches, function(match, i){ oldIndex[i] = _.indexOf(self.charCache[0], match); newIndex[i] = _.indexOf(self.charCache[1], match); }); // saves output in object self.indices = { oldIndex : oldIndex, newIndex : newIndex }; } FancyText.prototype.doAnimation = function() { var self = this; // TO DO: These can't always be done with hardcoded eq values var $topEl = self.config.$container.eq(1); var $bottomEl = self.config.$container.eq(0); // TO DO: Why doesn't self.charCache[1] have a value yet? // var range = _.range(self.charCache[1].length); var range = _.range(500); // find what elements aren't going to change var notShared = _.difference(range, self.indices.newIndex); /* Sets transition speed for all words/letters. This determines the speed that the matching letters/words will move at */ $('.char').css({transition: self.config.animationSpeed + 's'}); //================ FADE ANIMATION ================// if (self.config.animationType === 'fade') { /* fades out non-matching words/letters at twices the speed set by 'animationSpeed'. */ _.each(notShared, function(notShared, i) { $topEl.find('.char-' + notShared) .addClass('fadeOut') .css({transition: self.config.animationSpeed/2 + 's'}); }); /* find the parent element and fade old tweet out, but only after letter/word animation has completed */ setTimeout(function() { $topEl.parent() .fadeOut( self.config.animationSpeed * 1000 * .25 ); }, self.config.animationSpeed * 1000); //================ EXPLODE ANIMATION ================// } else if (self.config.animationType === 'explode') { var dHeight = $(document).height(); var dWidth = $(document).width(); _.each(notShared, function(notShared, i) { /* Everything here sucks. Rewrite it all. I want a number that's either the document height/width or a negative version of that. */ var topNeg = self._rdmInt(0, 1) > .5 ? false : true; var leftNeg = self._rdmInt(0, 1) > .5 ? false : true; $topEl.find('.char-' + notShared) .css({ top: topNeg === true ? self._rdmInt(-dHeight * 1.35, -dHeight) : self._rdmInt(dHeight, dHeight * 1.35), left: leftNeg === true ? self._rdmInt(-dWidth * 1.35, -dWidth) : self._rdmInt(dWidth, dWidth * 1.35), transition: self.config.animationSpeed * 2 + 's' }); }); setTimeout(function() { $topEl.parent() .fadeOut( self.config.animationSpeed * 1000 * .65 ); }, self.config.animationSpeed * 1000); } // TO DO: Make below code less of a clusterfuck // sets new top/left positions for each char-[i] class _.each(self.indices.oldIndex, function(oldIndex, i) { $topEl.find('.char-' + self.indices.newIndex[i]).css({ top : $bottomEl.find('.char-' + oldIndex).css('top'), left : $bottomEl.find('.char-' + oldIndex).css('left') }); }); } var makeFancyHappen = new FancyText({ $container: $('.text'), transitionTime: 2, animationSpeed: 1.25, animationType: 'fade', splitBy: 'letter' });