Splitting text into individual characters for animation I have code which makes use of jQuery and some additional libraries. But the jQuery part is too overwhelming (and thus it does its job slowly). So I tried to optimize it, but had no luck. Now I'm seeking help from the community.

Here is the code, from the news page in my WP theme:



/*
 CSS Reset
*/
   html,body,div,span,applet,object,iframe,audio,video,h1,h2,h3,h4,h5,h6,h7,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark {
 vertical-align: baseline;
}
#gsap-title-, #gsap-meta- {
    position: relative;
}
#gsap-title- span, #gsap-meta- span  {
 position: relative;
 display:inline-block;
 -webkit-font-smoothing:antialiased;
}
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"&gt;
&lt;meta name="viewport" content="width=device-width, initial-scale=1"&gt;
&lt;script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js"&gt;&lt;/script&gt;
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.inview/1.0.0/jquery.inview.min.js"&gt;&lt;/script&gt;
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.2/TweenMax.min.js"&gt;&lt;/script&gt;
&lt;script&gt;
jQuery(document).ready(function($){
var $startAnim = $('[id^=start-anim]');
var $exitAnim = $('[id^=exit-anim]');
var $title = $('[id^=gsap-title-]');
var $postmeta = $('[id^=gsap-meta-]');
$($title).each(function(){
 $(this).html($(this).text().replace(/./g, "&lt;span&gt;$&amp;&lt;/span&gt;").replace(/\s/g, "&amp;nbsp;"))
 .on('inview', function(event, isInView) {
    if (isInView) {
        // element is now visible in the viewport
  TweenMax.staggerFromTo( $(this).find("span"), 0.4, {autoAlpha:0, rotationX:-90, top:"-30px"}, {autoAlpha:1, rotationX:0, top:"0px"}, 0.1 );
 }
 else {
        // element has gone out of viewport
  TweenMax.staggerFromTo( $(this).find("span"), 0.4, {autoAlpha:1, rotationX:0, top:"0px"}, {autoAlpha:0, rotationX:-90, top:"-30px"}, 0.1 );
    }
  });
});
$($postmeta).each(function(){
 $(this).html($(this).text().replace(/./g, "&lt;span&gt;$&amp;&lt;/span&gt;").replace(/\s/g, "&amp;nbsp;"))
 .on('inview', function(event, isInView) {
    if (isInView) {
  TweenMax.staggerFromTo( $(this).find("span"), 0.09, {autoAlpha:0, scale:2}, {autoAlpha:1, scale:1}, 0.01 );
    }
 else {
  TweenMax.staggerFromTo( $(this).find("span"), 0.09, {autoAlpha:1, scale:1}, {autoAlpha:0, scale:2}, 0.01 );
   }
  });
  });
  
});
&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div id="gsap-title-"&gt;This is title&lt;/div&gt;
&lt;div id="gsap-meta-1"&gt;To start animation, click Run. &lt;/div&gt;
&lt;div id="gsap-meta-2"&gt;Then click here, or resize the height of this window (simulate 'inview' event). &lt;/div&gt;
&lt;/body&gt;






As you can see, this whole code is too overwhelming. Thus I tried to simplify the jQuery code.

I tried to process  $title and $postmeta with .html() simultaneously, and only then pass them to .on(). Thus :

$([$title, $postmeta]).each(function(){ // ...did work, but incorrectly - all $title and $postmeta were processed, but at the output - in each line containing  some id=gsap-title-, all matching elements gsap-title-1, gsap-title-2... gsap-title-n are displayed (the same as gsap-meta-), ...all of them, in each line ( e.g. merged in an unwanted manner);

Also I had a look at .add(), .concat() and .push() but according to examples, output is similar to .merge() method.

To sum up: current state - lots of code, lots of similar jQuery code, desired state - less jQuery code, with the same functionality.
There are a number of ways you can improve the javascript/jquery on this page.

If possible you should move all of your &lt;script&gt; references to the end of the HTML &lt;/body&gt; element.  This improves performance because the browser will wait to process the JS until after the HTML is loaded.  With the links in the head, the browser must download the JS and parse it before continuing to the HTML.

The next thing to do is encapsulate your code in an IIFE to create a private scope for your code (and to help prevent polluting the global scope). You should also add 'use strict'; to the top of your code to put yourself in JavaScripts' strict mode.  It helps prevent a lot of common issues.

(function($){
  'use strict';
  // other code here
})(jQuery);


This has the added benefit of allowing you to pass in the jQuery object to the function as $ and know that it will work as expected.

Another thing that can be done is removing the majority of your code out of the document.ready event.  You only really need to do DOM selections there (since you need the DOM to be ready before you can select the elements). Everything else can be moved to stand-alone functions.  This will not only make the code more readable, it will also help with debugging because you wont be having as many anonymous functions. 

Also, you are "diving into the DOM" a lot more than you need to.  For instance, you have this line of code:

$(this).html($(this).text().replace(/./g, "&lt;span&gt;$&amp;&lt;/span&gt;").replace(/\s/g, "&amp;nbsp;"))


So you are grabbing $(this) from the DOM twice.  This is one of jQuerys most expensive functions in terms of performance.  You want to do this as little as possible so you should cache your selctions to improve your overall performance.

You are also adding events to each $title and $postmeta.  It would be easier to use event delegation.  This will improve the overall performance of the page.

Last, you should DRY your code.  You have a lot of repeated code that can be abstracted out to more generic functions.

Here is the updated code:

(function($){

  'use strict';

  function cleanText(sel){
    $(sel).each(function(){
      var $t= $(this);
      var txt = $t.text().replace(/./g, '&lt;span&gt;$&amp;&lt;/span&gt;').replace(/\s/g, '&amp;nbsp;');
      $t.text(txt);
    });
  }

  function handleTitleInView(event, isInView ){
    var visible = {autoAlpha:1, rotationX:0, top:"0px"},
        hidden = {autoAlpha:0, rotationX:-90, top:"-30px"},
        $el = $(this).find('span');

    TweenMax.staggerFromTo( 
        $el, 0.4, 
        (isInView) ? hidden : visible, 
        (isInView) ? visible : hidden,
        0.1 
    );
  }

  function handleMetaInView(event, isInView ){
    var visible = {autoAlpha:1, scale:1},
        hidden = {autoAlpha:0, scale:2},
        $el = $(this).find('span');

    TweenMax.staggerFromTo( 
        $el, 0.4, 
        (isInView) ? hidden : visible, 
        (isInView) ? visible : hidden,
        0.1 
    );
  }

  function init() {
    var titles = '[id^=gsap-title-]';
    var postmeta = '[id^=gsap-meta-]';

    cleanText(titles);
    cleanText(postmeta);

    $(document).on('inview', titles, handleTitleInView);
    $(document).on('inview', postmeta, handleMetaInView);

  }

  $(function() { //shorthand for document.ready
    init();
  });

})(jQuery);


There are a couple of other optimizations you could apply (ex: use a for loop instead of each for better performance) but I'll leave those for you.

Here are a few links for some best practices:


jQuery Tips Everyone Should Know
Eliminate JavaScript Code Smells
Stop Writing Slow JavaScript
JavaScript Anti-Patterns


Hope that helps!