Key Takeaways
- Unobtrusive JavaScript promotes a clean separation of structure (HTML), presentation (CSS), and behavior (JavaScript), enhancing maintainability and scalability of web applications.
- Scripting should ideally be separated from HTML markup, utilizing external scripts and dynamically inserting markup via DOM methods to ensure functionality without JavaScript dependency.
- Loading JavaScript at the end of the document (back loading) rather than in the head section (front loading) avoids delays in page rendering and interaction, improving user experience.
- Techniques like hiding content via CSS until scripts are fully loaded and operational can prevent layout shifts and unresponsive elements, ensuring a smoother user interaction.
- Employing unobtrusive JavaScript can lead to better performance, accessibility, and SEO, as it allows web pages to function properly even if JavaScript is disabled or unsupported by the browser.
‘Tis the season to be jolly, and ’tis also an exciting time to be a JavaScript developer. With the advent of the Web 2.0 craze, a new breed of JavaScript developer was born. Modern JavaScript programmers take their trade very seriously and count themselves among the fraternity of “real” programmers. A key component in a JavaScript programmer’s arsenal is the methodology of unobtrusive JavaScript — the idea that a web page’s behavior should remain separate from its structure. The idea for unobtrusive JavaScript grew out of the Web Standards movement, which advocated that web pages should be separated into three layers — structure (HTML), presentation (CSS), and behavior (JavaScript) — and that each additional layer should enhance the previous one.
Traditionally, most, if not all, event-based JavaScript was written directly into the web page’s markup in the form of event handler attributes such as onclick
, onfocus
, onload
, onmouseover
, and onmouseout
. Also, all dynamically generated markup took the form of in-place document.write
statements. But none of this sits well with the principle of unobtrusive JavaScript.
Just as presents aren’t what Christmas is all about, neither is JavaScript what a web page is all about. A page should be functional without any scripting, rather being dependent on it. JavaScript function calls and instructions that are inextricably intertwined with markup create just such a dependency. They also reduce the portability of the document’s HTML and make it progressively harder to maintain as the site’s page count increases. But worst of all, they’ll get you onto Santa’s Naughty list — and no one wants to be there!
Buying the Presents
Unobtrusive JavaScript dictates that scripting should ideally reside in a separate document, and hook into a web page through HTML id
and class
attributes. Likewise, all dynamically generated markup should be inserted into the DOM after it has been built using made-for-the-purpose DOM methods. This way, if a page is already functional before JavaScript is added, the behavioral layer becomes an enhancement to the document rather than a dependency — kind of like icing on a cake, or gifts on Christmas.
Now we don’t live in an ideal world. Sometimes we find ourselves working with multi-generational projects that haven’t been well documented or maintained. Other times, our mandate (and therefore budget) doesn’t cover a complete revamp or optimization of the existing code that we’re being asked to modify. Web pages aren’t always light, networks aren’t always fast, and, in a team development setting, developers don’t always have full control over all of a page’s components. Keeping that in mind, let’s take a look at a nasty side effect of the implementation of unobtrusive JavaScript when conditions aren’t optimal.
Bringing the Presents Home Through the Front Door
There are two ways to load JavaScript into an HTML document. The traditional approach is to place a <script>
tag in the document’s head and trigger your functions using the window object’s onload
event. We’ll call this “front loading” because the scripts are loaded before the page’s contents in the document’s <head>
, before the DOM is built. Front loading isn’t a good idea because it’s vulnerable to timing issues. For example, a browser downloads, parses, and executes JavaScript wherever it’s encountered in the web page’s source, so any JavaScript in the document’s <head>
will delay the page’s rendering until that process is complete. More importantly, once that’s done and the page is rendered, functions tied to the window object’s onload
event may not be triggered right away. That’s because the event is only triggered once the browser has finished downloading all of the page’s dependencies — including the several hundred kilobytes’ worth of images and other media often found on web pages today.
Front loading can cause an undesired effect in which the visitor sees a full, JavaScript-free page for a period during which he or she is able to click on anything. So, for example, if an anchor was meant to trigger a modal popup (a div
on CSS steroids masquerading as a popup) it wouldn’t do so during this loading period, because the JavaScript required to set up the modal behaviour would not yet have executed, since the window object’s onload
event wouldn’t have fired. Instead, once the anchor was clicked, the browser would just send the user to the URI found in the anchor’s href
attribute. The end result would be that the page wouldn’t function as intended. Sure, having a valid URI in the anchor still lets the visitor continue to use the site, but it isn’t the desired or intended effect.
Here’s what a front-loaded, unobtrusive script looks like:
front-load.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "https://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Welcome</title>
<script>
function initSignin() {
var signin = document.getElementById("signin");
signin.onclick = function () {
/*
* Sign-in code that creates a modal
* popup goes here.
*/
alert('Pretend this is a modal popup...');
return false; // Stop the anchor's default behavior
};
}
window.onload = initSignin;
</script>
<link rel="stylesheet" type="text/css" media="all" href="style.css">
</head>
<body>
<p class="xmas">
<a href="/signin/" id="signin">Sign in</a>
</p>
<!-- 700 kilobytes worth of media goes here -->
</body>
</html>
You’ll note that the execution of our initSignin
function is deferred till after the page’s contents are loaded. Within the initSignin
function we stop the “Sign in” anchor’s default behavior by returning the value false to the anchor. However, the browser won’t trigger the window object’s onload event until it’s downloaded seven hundred kilobytes of media. So, until it’s done getting those files, initSignin
won’t run, and our link’s behavior won’t be overridden.
Sneaking the Presents in Through the Back Door
The second — and ideal — way to load JavaScript into an HTML document is to put all of our <script>
tags at the very end of the document, right before the closing </body>
tag. This allows us to be sure that the DOM is ready to be acted upon, since the code is being loaded after all of the <body>
‘s HTML is loaded into the DOM. Doing this eliminates the need for the window
object’s onload
event handler. It also greatly reduces the wait between the page’s rendering and the execution of our JavaScript, because its execution doesn’t depend on an event that’s fired only upon the completion of the download of all of the document’s dependencies. In this scenario, the code for the popup link would execute a lot sooner, and would probably already be in place before the visitor even considers clicking on the “Sign in” link.
Here’s what a back-loaded unobtrusive script looks like:
back-load.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "https://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Welcome</title>
<link rel="stylesheet" type="text/css" media="all" href="style.css">
</head>
<body>
<p class="xmas">
<a href="/signin/" id="signin">Sign in</a>
</p>
<!-- 700 kilobytes worth of media goes here -->
<script>
var signin = document.getElementById("signin");
signin.onclick = function () {
/*
* Sign-in code that creates a modal
* popup goes here.
*/
alert('Pretend this is a modal popup...');
return false; // Stop the anchor's default behavior
};
</script>
</body>
</html>
Note that there’s about seven hundred kilobytes of media between our link and our code, but that doesn’t matter, because the browser doesn’t load media sequentially like it does JavaScript. So it will fire off a handful of requests for media, but it will execute the JavaScript even while that operation is underway.
That said, there may still be problems, even with back loading.
Hide the Presents Till it’s Time to Give Them Out
It may happen that your page has a lot of JavaScript to process, or that the server hosting your scripts is experiencing a momentary lag. Even if you’re back loading your scripts, situations such as these may keep them from kicking in right away. This might result in strange behavior, such as the aforementioned links not being overridden in time, or even shifting layout problems. The latter issue occurs when you’re modifying the DOM via scripting — for example if you’re adding class names that will cause CSS rules to be applied, inserting elements into the DOM, or adjusting the position or dimensions of an existing element. If the JavaScript code runs even a little late, and the changes to the DOM occur after the initial rendering, the result will be that elements will shift on the page, or worse, text will appear briefly before being hidden by the tardy execution of a function.
A technique for dealing with the inevitability of this scenario is to hide the affected content prior to its being rendered. This would mean writing a CSS rule along the following lines:
.modal {
visibility: hidden;
}
We’d give the class name modal
to all the anchors in the page that are supposed to trigger a modal popup. We’d then write within our function a line of code that overrides the anchors’ default behavior so that once it’s done its work, it sets the anchor’s visibility to visible, like so:
el.style.visibility = "visible";
We need to be careful, though, not to create new problems while solving other ones. By setting the visibility of all of the links with the modal class name in the page to hidden, we risk locking out anyone who doesn’t have JavaScript available. That risk exists because the mechanism responsible for hiding the links is CSS, and the mechanism responsible for making them visible is JavaScript. By spanning two of the layers of separation, we’re assuming that “everyone who has CSS also has JavaScript,” which isn’t always the case. So, what we need to do is create the modal style rule using JavaScript. That way, if JavaScript isn’t available, the rule is never created and the links are never hidden. This is a situation where back loading is a bad idea because we want that rule to be available as soon as possible. Here’s what our “Sign in” example page would look like if we used this technique:
hide-content.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "https://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Welcome</title>
<link rel="stylesheet" type="text/css" media="all" href="style.css">
<script>
document.write('<style type="text/css">.modal {visibility: hidden;}</style>');
</script>
</head>
<body>
<p class="xmas">
<a href="/signin/" id="signin" class="modal">Sign in</a>
</p>
<!-- 700 kilobytes worth of media goes here -->
<script>
var signin = document.getElementById("signin");
signin.onclick = function () {
/*
* Sign-in code that creates a modal
* popup goes here.
*/
alert('Pretend this is a modal popup...');
return false; // Stop the anchor's default behavior
};
signin.style.visibility = "visible";
</script>
</body>
</html>
You’ll note that I’ve used document.write
in order to create the modal style rule. Though I never advocate the use of document.write
, this is the one place where I’m prepared to make an exception. This example uses a <style>
block, but what I’d normally use on a real site would be an external CSS document that would contain all of the rules that can’t be undone without JavaScript — such as visibility: hidden
. Writing the <link>
tag that calls that CSS document with document.write
is a simple, one-line solution to making sure that the browser calls that file while it’s still processing the contents of the <head>
(if JavaScript is available).
You’ll also note that I’ve added a line that resets the anchor’s visibility right after I’ve assigned a function to its onclick
event handler. In other words, now that I’m sure that the anchor will behave as I want it to, I can turn it back on.
There are many ways to show and hide content, and each is valid in particular contexts. In this situation, I’ve opted to use visibility: hidden
because it preserves the element’s dimensions while hiding it. Were I to use display: none
, for example, the space that the anchor normally occupies would collapse, and turning it on would cause the layout of the document to shift slightly. Another technique for hiding and showing content is to set an element’s position to absolute
and its left value to -3000px
, sending it off the left edge of the screen. Bringing it back is as easy as either setting its position to relative or static, or giving it a left value that will bring it back into the viewable area of the page.
Wrapping the Presents
So we’ve back loaded our JavaScript and hidden the content that our code affects, but just popping it onto the screen isn’t very graceful, and it gives the visitor absolutely no indication that there’s some content on its way. It’s kind of like Christmas presents: you don’t keep them unwrapped and in your closet until it’s time to hand them out. You wrap them up and leave them out so that people know that they’ve got something coming their way. The same applies to content that you’re processing but keeping hidden. The most common way to indicate that something’s coming is to use an animated graphic as a visual cue.
Let’s add a loader to our “Sign in” anchor:
loader.css
.modal {
background: url(loading.gif) no-repeat center left;
}
.modal a {
visibility: hidden;
}
loader.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "https://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Welcome</title>
<link rel="stylesheet" type="text/css" media="all" href="style.css">
<script>
document.write('<link rel="stylesheet" type="text/css" href="loader.css">');
</script>
</head>
<body>
<div class="xmas">
<p class="modal">
<a href="/signin/" id="signin">Sign in</a>
</p>
<!-- 700 kilobytes worth of media goes here -->
<script src="loader.js"></script>
</div>
</body>
</html>
loader.js
var signin = document.getElementById("signin");
signin.onclick = function () {
/*
* Sign-in code that creates a modal
* popup goes here.
*/
alert('Pretend this is a modal popup...');
return false; // Stop the anchor's default behavior
};
signin.style.visibility = "visible";
signin.parentNode.style.background = "none";
The first thing that I’ve done here is to create separate CSS and JS files, because our example has grown, and it’s always better to keep CSS and JavaScript in separate files. I’ve added a new CSS background rule that adds a loader graphic to the parent element of our “Sign in” anchor. So while the anchor is hidden, its parent element displays a rotating graphic that indicates that something will be occupying this space momentarily. I’ve also moved the modal class name up to the anchor’s parent element, since we need it to hold our loading graphic. Lastly, I’ve added one more instruction to our code block; it removes the loader graphic once our onclick
assignment operation is complete.
Since this example is so small, you’re unlikely to ever experience a delay that’s long enough to allow you to see the loader graphic. For this reason, I’ve put together an example that simulates a two-second delay so that you can see the loader in action.
Wrapping Everything Else
This technique isn’t only limited to textual content; we can also add loaders to images. Instead of manually triggering the switch from loader to content, however, we’ll set up an event handler that detects when the browser is finished downloading the image. We’ll do this through its onload event handler. Once the event is triggered by the browser, our code will handle the switch.
In this example, we’ll do things a little differently just so we can explore the different implementation possibilities. In the earlier examples, we manipulated an element’s style object directly through JavaScript. This approach may not always be appropriate, as designers may want more direct control over the different states of an element through CSS. So for this example, we’ll be defining a loading class name that will be assigned to elements that are, well, loading. Once the loading is complete, all we’ll do is remove the class name.
Let’s start with the markup:
loader-img.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "https://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Welcome</title>
<link rel="stylesheet" type="text/css" href="loader-img.css">
<link rel="stylesheet" type="text/css" media="all" href="style.css">
<script>
document.write('<link rel="stylesheet" type="text/css" href="loader-img-js.css">');
</script>
</head>
<body>
<ul id="thumbnails">
<li class="loading"><img src="img1.jpg"></li>
<li class="loading"><img src="img2.jpg"></li>
<li class="loading"><img src="img3.jpg"></li>
<li class="loading"><img src="img4.jpg"></li>
<li class="loading"><img src="img5.jpg"></li>
<li class="loading"><img src="img6.jpg"></li>
<li class="loading"><img src="img7.jpg"></li>
<li class="loading"><img src="img8.jpg"></li>
<li class="loading"><img src="img9.jpg"></li>
<li class="loading"><img src="img10.jpg"></li>
<li class="loading"><img src="img11.jpg"></li>
<li class="loading"><img src="img12.jpg"></li>
<li class="loading"><img src="img13.jpg"></li>
<li class="loading"><img src="img14.jpg"></li>
<li class="loading"><img src="img15.jpg"></li>
</ul>
<script src="loader-img.js"></script>
<p class="caption"><strong>Image Credit:</strong> <a href="http://www.sxc.hu/profile/danzo08/">Daniel Wildman</a></p>
</body>
</html>
What we have here is a simple list of images. Each list item is given the class name loading, since we know that at the time of the DOM’s creation, the images have yet to be downloaded.
We’ve also included two CSS files: one that contains basic layout rules, and another, linked via a JavaScript document.write statement, which hides content that will be made visible later by JavaScript:
loader-img.css
#thumbnails {
list-style-type: none;
width: 375px;
}
#thumbnails li {
width: 125px;
height: 125px;
float: left;
}
loader-img-js.css
#thumbnails li.loading {
background: url(loader-big.gif) no-repeat center center;
}
#thumbnails li.loading img {
visibility: hidden;
}
Lastly, and most importantly, here’s the script that implements a loader for each of our images:
loader-img.js
var thumbs = document.getElementById("thumbnails");
if (thumbs) {
var imgs = thumbs.getElementsByTagName("img");
if (imgs.length > 0) {
for (var i = 0; imgs[i]; i = i + 1) {
var img = imgs[i];
var newImg = img.cloneNode(false);
img.parentNode.insertBefore(newImg, img);
newImg.onload = function () {
var li = this.parentNode;
li.className = li.className.replace("loading", "");
};
newImg.src = img.src;
img.parentNode.removeChild(img);
}
}
}
Here, we grab the container element that surrounds our thumbnails, and all the images within it. Now, normally we’d just loop over the images and assign an onload
event handler to each of them. Unfortunately, Firefox doesn’t fire the onload
event on images that are already in the cache, so our script won’t work on subsequent visits to the page. In order to work around this issue, we simply clone the image, and replace the original with its clone. The act of inserting the newly cloned image into the document ensures that its onload event will fire.
Another point to note is that Internet Explorer and Opera require that the onload
event handler be assigned before the src
attribute. Otherwise, they won’t fire the onload
event. When the event is triggered, the script will remove the class name loading from the image’s parent element. This, in turn, causes the list item to lose its rotating background image, and the image to lose the visibility: hidden;
declaration that was hiding it. In my opinion, class name manipulation is by far the most elegant way to toggle an element’s style, because it keeps all of the presentation information in a separate file that’s dedicated to the task. It also allows future changes to be made to style rules via modifications to the CSS — without requiring us to open up a JavaScript file.
In case this example runs too quickly for you, I’ve put together another one that simulates a random delay, so you can get an idea of what this example would look like on a slower connection.
Post-tryptophan Adventures
We’ve explored a few of the ways to safely deal with unobtrusive JavaScript timing issues. We’ve also looked at how to deal with timing issues in general by incorporating loaders as placeholders for our hidden content. I hope this tutorial has encouraged you to think creatively and unobtrusively (don’t forget to download the complete code). Now take what you’ve learned here and create wonderfully unobtrusive, gracefully degradable, and joyous web sites. Ho! Ho! Ho!
Frequently Asked Questions about Unobtrusive JavaScript
What is the main difference between obtrusive and unobtrusive JavaScript?
The primary difference between obtrusive and unobtrusive JavaScript lies in how they interact with the HTML of a webpage. Obtrusive JavaScript directly embeds the script into the HTML, which can lead to cluttered code and potential issues with accessibility and page load times. On the other hand, unobtrusive JavaScript separates the JavaScript from the HTML, leading to cleaner code, improved accessibility, and potentially faster page load times.
Why is unobtrusive JavaScript considered better practice?
Unobtrusive JavaScript is considered better practice because it promotes separation of concerns, meaning that the structure (HTML), style (CSS), and behavior (JavaScript) of a webpage are kept separate. This makes the code easier to manage, debug, and update. It also improves accessibility, as the webpage can still function even if JavaScript is disabled or not supported by the user’s browser.
How can I convert my obtrusive JavaScript to unobtrusive JavaScript?
Converting obtrusive JavaScript to unobtrusive JavaScript involves moving your JavaScript code from inline HTML to external JavaScript files. This includes event handlers, which should be attached using JavaScript methods like addEventListener, rather than being included directly in the HTML elements.
What are the principles of unobtrusive JavaScript?
The principles of unobtrusive JavaScript include: separation of structure from behavior, keeping the webpage accessible without JavaScript, and avoiding the use of JavaScript for things that can be done with CSS or HTML.
How does unobtrusive JavaScript improve website performance?
Unobtrusive JavaScript can improve website performance by reducing the amount of code that needs to be downloaded and parsed by the browser. By keeping the JavaScript separate from the HTML, the browser can cache the JavaScript file, meaning it only needs to be downloaded once, rather than every time the page is loaded.
Can unobtrusive JavaScript improve SEO?
Yes, unobtrusive JavaScript can improve SEO. By keeping the JavaScript separate from the HTML, search engines can more easily crawl and index the content of the webpage. Additionally, unobtrusive JavaScript can improve page load times, which is a factor in search engine rankings.
Is unobtrusive JavaScript compatible with all browsers?
Unobtrusive JavaScript is compatible with all modern browsers. However, for older browsers that do not support certain JavaScript methods, you may need to include polyfills or fallbacks to ensure the webpage still functions correctly.
How does unobtrusive JavaScript enhance user experience?
Unobtrusive JavaScript enhances user experience by improving page load times and ensuring the webpage is still functional even if JavaScript is disabled or not supported. This can be particularly beneficial for users with slow internet connections or those using assistive technologies.
Can I use JavaScript libraries and frameworks with unobtrusive JavaScript?
Yes, JavaScript libraries and frameworks can be used with unobtrusive JavaScript. In fact, many popular libraries and frameworks, such as jQuery and AngularJS, encourage unobtrusive JavaScript practices.
What are some resources for learning more about unobtrusive JavaScript?
There are many online resources for learning more about unobtrusive JavaScript, including tutorials, blog posts, and documentation on websites like W3Schools, Mozilla Developer Network, and Stack Overflow. Additionally, there are several books available on the subject, such as “Unobtrusive Ajax” by Jesse Skinner.
Ara has been working on the Web since 1997. He's been a freelancer, a webmaster, and most recently, a front-end architect and practice lead for Nurun, a global interactive communications agency. Ara's experience comes from having worked on every aspect of web development throughout his career, but he's now following his passion for web standards-based front-end development.