Friday, February 16th, 2007

Understanding and solving the JavaScript/CSS entanglement phenomenon

Category: Accessibility, Unobtrusive JS

<p>Ara Pehlivanian talked about the graceful degradation myth awhile back, and since then has come up with a simple solution to his problem.

The key is setting CSS styles aimed at JS enabled browsers that overwrite the initial state in the document that does the right thing for someone who isn’t coming in via a JavaScript enabled environment (e.g. accessible).

Here is an example of a window that drops down content in JS mode, and shows it without thus not blocking that content from non-JS view.

The CSS

Brand CSS for items that you want to override via hasJS classnames.

  1. body.hasJS {
  2.     background-color: #0f0;
  3. }
  4.        
  5. body {
  6.     background: #f00;
  7. }

JavaScript

Turn on that CSS via JavaScript (addClassName):

  1. <script type="text/javascript">document.body.className += "hasJS"</script>
  2. <div class="window">
  3.     <div class="head">
  4.         <h1 id="toggle">This is a window head</h1>
  5.     </div>
  6.     <div class="content" id="toggleContent">
  7.         <p>This is the windows contents.</p>
  8.         <p>And some more content...</p>
  9.     </div>
  10. </div>

Do you use these tactics?

Related Content:

Posted by Dion Almaer at 12:01 am
17 Comments

++++-
4 rating from 27 votes

17 Comments »

Comments feed TrackBack URI

wow, very clever ^^

I think I will use that in the future quite often ;)

Comment by Florian — February 16, 2007

I found this trick in Drupal, in /misc/drupal.js.

// Global Killswitch on the element
if (isJsEnabled()) {
document.documentElement.className = 'js';
}

The code is taken from 4.7.6.

Comment by Wiktor — February 16, 2007

You can do the same thing with browser names and versions. This lets you specify CSS workarounds for the JavaScript version without relying on CSS hacks.

The code posted does have a bug though: by simply adding “hasJS” to the class name, it will affect classes already there. So “foo” becomes “foohasJS”.

Comment by Mark Wubben — February 16, 2007

I think updated solution is better, without script inside the body, just “classing” the documentElement. Simple and clear.

Comment by Andrea Giammarchi — February 16, 2007

I’m using this for my current project. Works great. Except you might initially see the non-js version for a fraction of a second (even when called onDomReady).

You also have to use a “addClass”-function, because the body might already have one or more classes.

Comment by Edwin — February 16, 2007

yup. .. i am used these tactics and even more: Besides hiding/showing JS related elements, i also save the non-js-user from downloading my javascript files – since he is not going to parse them anyways. ;)
This saves traffic and speeds up the page loads. Once that is handled i try shaping the load precedure by sorting out what scripts are needed first and where.. aka. intelligent script loading.

Comment by Kjell — February 16, 2007

My personal preference is to do this:

var css = document.createElement(‘LINK’);
// set attributes, append to head

That way I can just manage two seperate stylesheets which usually @import a third common one.

Comment by Mark Kahn — February 16, 2007

Um…let’s try this again…

[noscript]
[link rel="stylesheet" href="my.css" type="text/css"]
[/noscript]
[script]
var css = document.createElement(‘LINK’);
// set attributes, append to head
[/script]

Comment by Mark Kahn — February 16, 2007

I’ve used a similar trick to hide and show interfaces which require javascript. Create a .show CSS selector that has a default rule of display: none; Then, use javascript to include a second, separate .css file with two rules: .show { display: block; } and .hide { display: none; }. Then, on pages which need to hide a whole portion of code from non-javascript browsers, put the javascript-requiring code in the .show element (which will be hidden if javascript doesn’t include the “extra” .css file) and messages and/or other interface elements within the .hide element (shown unless javascript hides it). Essentially the same concept as above, just executed somewhat differently.

Excellent post. Thanks!

Comment by Dashifen — February 16, 2007

I use the same tactics as described here since a while back. It seems as obvious as the kind of test you run to detect browser abilities:

really short example:

if(!document.getElementById) return false; // and thus kill all the script
if($('hideMe')) $(hideMe).className += " hidden"; // hide choosen elem.

Comment by sam — February 16, 2007

Chris Heilmann: I guess I missed that post. Interesting how people come out of the woodwork with solutions that are similar (if not identical) that they probably came to on their own :-).

Mark Kahn: I’m leaning more towards that approach since it allows for the storage of js only css in a separate file and removes the need for prefixing every rule with “hasJS”.

Comment by Ara Pehlivanian — February 16, 2007

You can also append this to the HEAD within the first nested script block pretty reliably, eg. getElementsByTagName(‘head’)[0].className = .. and then match with CSS, head.isIE div {}, and so on. I believe someone from MSFT published a related article on filtering (as Mark Wubben mentioned) via class name this way last year. It’s pretty effective.

Comment by Scott Schiller — February 16, 2007

This situation is one of the few exceptions where document.write is really useful. I like to set up the HTML and CSS as if it’s all the browser can handle, and then document.write an override stylesheet with JS — which usually is just a bunch of #element { display:none; } statements. It looks like this:

IN HEAD (all unobtrusive external files):
1. CSS stylesheet for JS-disabled browsers
2. JS that document.writes an override stylesheet
3. JS for rearranging/behavior

IN BODY:
Clean (X)HTML markup for JS-disabled browsers/search engines

Comment by Peter Frueh — February 16, 2007

I do this all the time. coupled with Dean’s window.onload solution it looks something like:

var dynPage = {
DOMavailable : null,
init:function() {
// quit if this function has already been called
if (arguments.callee.done) return;

// flag this function so we don't do the same thing twice
arguments.callee.done = true;

// kill the timer
if (_timer) {
clearInterval(_timer);
_timer = null;
}

dynPage.DOMavailable = true;
dynPage.enable();

// load the other functions
},

enable:function() {
document.body.className += ' dynamic';
}
}

Comment by michael — February 16, 2007

Want another method (not necessarily better) ?
Add the js only stylesheet as an alternate stylesheet. Then use javascript to enable it. More about it here (posted in April 2005:-) :
http://www.formassembly.com/blog/javascript-enabled-stylesheets/

Comment by cedsav — February 16, 2007

I’m using this method to switch skins

Comment by noname — February 16, 2007

I prefer not supporting non-js browsers. F ‘em.

Comment by joe — February 16, 2007

Leave a comment

You must be logged in to post a comment.