Let’s say you have a page where the presentation dependencies are the javascript code. Usually, all your style tags are placed in the header prior to the remaining elements are rendered. Your javascript is a JQuery plug-in (e.g., some-plugin.js)that takes some element and does some kind of styling to it by applying some CSS classes using its own CSS file(e.g., some-plugin.css).
<!-- some-plugin.js's own styles --> <link href="some-plugin.css" rel="stylesheet" type="text/css" /> <!-- the target element --> <div id="targetOfPlugin"></div> <!-- the plug in -->
You run the page and everything renders fine. Your page is deployed and no worries. However, one day you decide to add ads on your page from a new affiliate.
<!-- some-plugin.js's own styles --> <link href="some-plugin.css" rel="stylesheet" type="text/css" /> <!-- the target element --></pre> <div id="targetOfPlugin"></div> <pre> <!-- new affiliate's ad source --> <script type="text/javascript" src="http://example.com/affiliate-ad-code.js"></script> <!-- new affiliate's ad code being used --><script type="text/javascript"> //writes ads in this location showAds(); </script> <!-- the plug in -->
You deploy your code and find that the page runs fine. However, time passes, and you notice that the affiliate’s ad causes a delay in loading the page. It is random based upon upon many possible things like loading images. Such a dependency can cause loading delays of elements on the page if the ad script was needed on the page closer to the top, above your javascript frameworks. The delay will prevent your javascript code from manipulating elements upstream on your document because your code’s placement below the offending ad. Some of the remedies for can be moving your javascript above the elements or slow-loading resource. The only issue with this is making your your code is executed on the DOM ready event. This means that as your page is still rendering elements, your code sits there until the event is raised that all elements have been loaded. Therefore, your target element of the plug in is looking plain Jane– not if we can help it!
The beauty of CSS is the hierarchical setup of how your style rules are applied. For example, a DIV’s style is only applied if its hierarchical representation meets the style rules applied.
/* The border will only be yellow if it is a direct descendant of the BODY tag*/ body > div {border: 1px solid yellow;}
For those of you who know CSS, this information will bore you. However, this key information is lost on may programmers who rather skirt around working with CSS. I used to be one of those guys!!! However, as user becomes more savy with web application UI’s, javascript and CSS are on constant collision courses. However, the beauty of CSS rules make for some interesting ways to gracefully introduce elements waiting for their code to execute and apply styles on them.
Let’s take the above example’s idea and make another example of it. Let’s say we have the following…
Not very presentable huh? However, our goal is to add more items to this list and make it styled to something that is less vanilla like the following.
Not the greatest but the point is to introduce the ‘better’ list with its new items. Most people like to use JQuery methods like fadeIn(), show(),css(), etc. I like using addClass(), removeClass(), or toggleClass(). The reason is that my changes would only be applied to the CSS involved. Using css() is too granular for this use case. fadeIn() and show() applies style changes directly as well. In the case of introducing the elements, we can manipulate the styles applied by either removing the applied hierarchical class setup for the CSS rules.
Taking the HTML that spits out the previous screenshot, we have this.
<div id="container"> <ul id="someList"> <li>123</li> <li>123</li> <li>123</li> <li>123</li> </ul> </div>
To simulate a 2 second delay in loading of external resources, we will use the javascript function setTimeout(). Let’s initially hide our list, add some more items, and then show it. When shown, we will use a class called formatted which will have a green dotted border, grayish background, and do some funky width transitions using CSS3 (won’t work well on older IE browsers).
Here’s the javascript:
//represent the delay from some resource setTimeout(function () { var someList = $('#someList'); ///add 50 li elements for (var i = 0; i < 50; i++) { $('</pre> <ul> <li>' + i + '</li> </ul> <pre> ').appendTo(someList); } //apply a class someList.addClass('formatted'); }); }, 2000);
and here is the CSS:
#container > ul.formatted li { height: 25px; width:200px; border: 1px dotted green; background-color: #efefef; -moz-transition:width 400ms, background-color 400ms; -webkit-transition:width 400ms, background-color 400ms; -o-transition:width 400ms, background-color 400ms; transition:width 400ms, background-color 400ms; } #container > ul.formatted li:hover { width: 400px; background-color: #e2feff; }
A lot of things going on! Well, we are going to do two thing: make CSS3 do a lot of grunt work and allow us to minimize the usage of javascript. Let’s change the CSS to the following:
#container > ul > li { width:200px; } #container.hidden-list > #someList > li { height: 0px; overflow: hidden; } #container.formatted #loader{display: none;} #container.formatted > ul { list-style: none; } #container.formatted > ul > li { height: 25px; border: 1px dotted green; background-color: #efefef; -moz-transition:height 1s, width 400ms, background-color 400ms; -webkit-transition:height 1s, width 400ms, background-color 400ms; -o-transition:height 1s, width 400ms, background-color 400ms; transition:height 1s, width 400ms, background-color 400ms; } #container.formatted > ul > li:hover { width: 400px; background-color: #e2feff; }
I created two classes: formatted and hidden-list. hidden-list class will also hide our ugly list. The formatted class will style our list. However, the rule is that we need to remove hidden-list class to show the formatted list.
The javascript now looks like the following:
setTimeout(function () { var someList = $('#someList'); ///add some elements for (var i = 0; i < 50; i++) { $('<ul> <li>' + i + '</li> </ul> ').appendTo(someList); } //unveil our work by removing the class 'hidden-list' $('#container').toggleClass('formatted hidden-list'); }, 2000);
The toggleClass() will swap out the class if it is assigned to the element or swap in the class if it not assigned.
Are we done? Not really. If you notice there is a weird transition of height for the existing list items while the newly added list items are set at specific height (no transition) when the code runs. This is because the style needs to be computed to its actual height. This called reflow and is done when an element’s geometry or position needs to be calculated. In this situation, reflow doesn’t happen. Preventing unnecessary reflow from occurring helps in the performance of the DOM. What we can do is call height() on each element after it is appended.
setTimeout(function () { var someList = $('#someList'); ///add some elements for (var i = 0; i < 50; i++) { //purposely call reflow $('<ul> <li>' + i + '</li> </ul>').appendTo(someList).height(); } //unveil our work by removing the class 'hidden-list' $('#container').toggleClass('formatted hidden-list'); }, 2000);
One more thing…because of reflow, we should add a more specific selectors to our CSS. The reason is the browsers read from right-to-left. If a browser read from left to right, the area of coverage could be big verses starting at the end and working its way up to the first found parent. Direct descendant operator, >, will help us with this. The following is our revised CSS:
#container > ul > li { width:200px; } #container.hidden-list > #someList > li { height: 0px; overflow: hidden; } #container.formatted > ul { list-style: none; } #container.formatted > ul > li { height: 25px; border: 1px dotted green; background-color: #efefef; -moz-transition:height 1s, width 400ms, background-color 400ms; -webkit-transition:height 1s, width 400ms, background-color 400ms; -o-transition:height 1s, width 400ms, background-color 400ms; transition:height 1s, width 400ms, background-color 400ms; } #container.formatted > ul > li:hover { width: 400px; background-color: #e2feff; }
On a final note,
The final version is found at this jsfiddle
In a future article, I will discuss more on reflow and how we could further optimize the style rules in this example.