DOM CSS
Individual element styles
- ICEbrowser and Escape/Evo cannot read or write the float style.
- NetFront 3.5 beta fails throws errors when setting className.
As described in the section on DHTML, DOM compatible browsers support the style object. The style object was created by Microsoft and although only became a standard in DOM Level 2 CSS, there was nothing to replace it in browsers supporting earlier DOM implementations, so it has been universally adopted by all DOM browsers. Through the style object, DOM browsers allow all styles the element accepts to be changed, not just those I described in the section on DHTML. It is equivalent to reading and writing the value of the style attribute, so styles can only be read if they were set using that attribute. Styles are almost all read/write.
The syntax is simple.
Simply write element.style.styleName.
If the style name consists of two or more words separated by hyphens '-', remove the hyphen and capitalise the first letter
of the word following it. For example, background-color becomes backgroundColor. When setting a value, it should be set as a string. Other data types will be converted to strings before setting the value to that string. Although most browsers will allow you to delete a style by setting it to null, a browser could legitimately set it to the string value 'null'
, and that would have an unwanted effect. If you want to delete a style, set it to an empty string, and never set it to null.
The only odd style is float, which is a reserved word in many languages, including JavaScript. As a result, the float style must be set using cssFloat in standards compliant browsers, and styleFloat in Internet Explorer 8- and 9+ in quirks mode (some standards compliant browsers also provide this as well). Simply set both of them. It will not cause any problems, and will work in all possible browsers.
The W3C DOM does provide an alternative way to change the styles on individual elements. The class attribute of the element can be removed using this:
element.className = '';
And it can be changed using:
element.className = 'newClassName';
Reading applied element styles
- getComputedStyle and currentStyle are not supported by Safari 2-, Konqueror 3.4- or Tkhtml Hv3.
When CSS stylesheets are applied to a page, elements may be targetted by several style rules, and inherit various styles from their ancestors. They will also receive some default styling from the browser's internal stylesheet. The DOM specification offers a way to work out what the result of those styles was, using the window.getComputedStyle method. This is supported by Opera 7+, Mozilla, iCab 3+, Konqueror 3.5+, and Chrome/Safari 3+, as well as Internet Explorer 9+ in standards mode. Internet Explorer also offers an alternative that also works in quirks mode; the currentStyle property of an element. This is supported by Internet Explorer on Windows and Mac, Opera 9+ and iCab 3.
getComputedStyle requires two parameters. The first is a reference to the element. The second is either the name of a pseudo element (':before', ':after', ':first-line'), or null just for the element itself. Since currentStyle only allows you to obtain the style for the element itself, that is all I will show here. In most cases, the values returned by these are the same, but there are a few cases where they may be different. For example, a table may have a width set by the CSS, but may be stretched wider to fit its contents. In this case currentStyle will still return the width specified by the CSS, but getComputedStyle will return the new stretched width.
var oElement = document.getElementById('mydiv'), oColor;
if( window.getComputedStyle ) {
oColor = window.getComputedStyle(oElement,null).color;
} else if( oElement.currentStyle ) {
oColor = oElement.currentStyle.color;
}
As well as being an actual usable value, the returned value for several styles may be their default values, such as 'auto', 'normal', or 'inherit'. The browser may also choose to return values in any available unit. For example, even if the CSS specifies a length in 'em' units, the browser may still return the equivalent value in 'px' units.
In cases where currentStyle gives a different result to getComputedStyle, Internet Explorer offers an alternative; runtimeStyle. This is an object almost exactly like currentStyle, but it gives a computed value instead of a cascaded value. However, it does not always give a usable value, and often gives a different kind of result to what would be expected from getComputedStyle, if it gives any value at all. It often computes a blank string for unspecified values, instead of their default values. Use this with care and only if you are sure it gives the result you need.
It is also possible to obtain some details of an element using its offset, client, and scroll values. These are all available as properties of the element, and give its current dimensions in pixels, not the styles that apply to it. This means that they also give real dimensions, even if the CSS value is 'auto'. These are not reliable for inline elements, and should only be used for elements that have block or equivalent display. Note that these properties were not standardised for a long time (they will be part of future DOM standards), but they work fairly reliably cross browser.
- offsetWidth and offsetHeight
- The dimensions of the element taken outside its border. (The width inside the padding in IE's quirks mode.)
- clientWidth and clientHeight
- The dimensions of the element taken inside its border, and minus any scrollbar size.
- scrollWidth and scrollHeight
- The dimensions the element would be using if it did not have a scrollbar and was not constrained in size (in other words, the dimensions of its contents, plus its padding). Only usable and reliable if the element actually has a scrollbar.
- scrollLeft and scrollTop
- The distance the element has been scrolled.
Rewriting stylesheets
The DOM allows the stylesheets themselves to be changed. Some browsers allow stylesheets to be modified, some also allow rules to be created or removed. This will be covered in the next chapter of this tutorial.
Changing the href of a linked stylesheet
The href of the stylesheet can be read in some browsers using the href property. Opera 7+, Mozilla, Konqueror, Safari/Chrome and Internet Explorer 5+ can set the href of the link element using setAttribute on the link tag, and Internet Explorer 4+ can also set the href of the styleSheets[index].
Switching stylesheets
- This is supported correctly by IE 4+ Win, IE 5+ Mac, Gecko (Mozilla/Firefox/Netscape 6+), and Opera 9+.
- This is supported incorrectly by KHTML/WebKit (Konqueror 3+/Safari/Chrome/OmniWeb 4.5+) and ICEbrowser.
- This is supported almost correctly by IE 4 Mac.
- This is supported correctly using a different syntax by Opera 7+ and iCab 3.
- This is supported correctly using a different syntax by KHTML/WebKit.
- iCab 3 fails to stop applying generated content and HTML element color from the preferred stylesheet when it is disabled.
The stylesheet can be disabled/enabled in some browsers using the boolean disabled property, but some browsers will only allow you to change the disabled property if the title attribute is set. To create the most reliable cross browser effect, the function I have written (below) will only try to enable / disable a stylesheet if its title attribute has been set.
The best use of the disabled property is to allow your readers to change the look of the site to suit their personal tastes or accessibility requirements. This requires them to be able to switch between the different stylesheets that you provide. Most often, this technique is used along with cookies, storing the user's preference of stylesheet as they leave the page (via the onunload event) and then using it again next time they view the page (using the onload event).
Setting up the HTML
To do this, set out your document as follows:
Firstly, if you need one, create a persistent stylesheet with all global styles, using this:
<link rel="stylesheet" type="text/css" href="all.css">
As it has no title attribute, it will never be disabled. You do not have to define this stylesheet if you do not want to, but if you do, remember that all styles you define in it will be used in all stylesheet modes, and will be used along with any stylesheet you later use by stylesheet switching.
The default switchable (preferred) stylesheet is set up using this:
<link rel="stylesheet" type="text/css" href="default.css" title="Default">
This will also be used by browsers that cannot disable/enable stylesheets and ensures that the default view will look good. You can then set up all alternative stylesheets using this:
<link rel="alternate stylesheet" type="text/css" href="extraPretty.css" title="Pretty">
<link rel="alternate stylesheet" type="text/css" href="bigFont.css" title="Big Font">
<link rel="alternate stylesheet" type="text/css" href="contrast.css" title="High Contrast">
Because these stylesheets are set up using 'alternate stylesheet' for the 'rel' attribute, lesser browsers will not use these stylesheets at all, and in browsers that can switch stylesheets, these stylesheets will be disabled by default.
Changing the stylesheets with the browsers' view menu
Opera 7+, Mozilla, Internet Explorer 8+, Konqueror 3+ and iCab 3+ will allow users to choose from these alternative stylesheets in the 'view' menu. However, this setting is not remembered by the browser (except by Konqueror), so the user will have to choose this for every page they view on your site. Also, other 'good' browsers, like Internet Explorer 7- and Safari/Chrome do not allow the user to choose stylesheets in any menus, so you will have to use the DOM to change them manually. Note that when using the view menu or the DOM technique, stylesheets that share the same title will be treated as if they were the same stylesheet and will be switched together.
Using the view menu only allows you to select stylesheets whose titles are identical. If you want to enable combinations, you would either have to produce combined stylesheets or include the same sheet multiple times with different titles to allow all combinations. This is also a problem with most basic stylesheet switching scripts. The script I will show you allows you to choose a combination of stylesheet titles, making it superior to most other scripts.
Referencing the stylesheet's disabled property and title property
document.stylesheets
Microsoft first came up with the document.styleSheets collection. The W3C decided this was a good idea and introduced it in DOM Level 2 CSS. All stylesheets are available in this collection, even if the rel attribute is set to 'alternate stylesheet'. Setting the disabled property of the collection entry disables and enables the stylesheet, and the title attribute is available here as a JavaScript property.
- Internet Explorer 4+ Win and 5+ Mac correctly support document.styleSheets.
- Internet Explorer 4 Mac supports document.styleSheets but does not provide the title attribute. Instead, the title of the style or link element must be used: document.styleSheets[0].owningElement.title.
- Gecko (Mozilla/Firefox/Netscape 6+) correctly supports document.styleSheets.
- Opera 9+ correctly supports document.styleSheets.
- Konqueror and Safari/Chrome [KHTML/WebKit] incorrectly only populate the document.styleSheets collection with stylesheets that are enabled at any particular instant. The stylesheets cannot be disabled from here, and do not have the title property.
- ICEbrowser populates the collection and allows the stylesheet to be enabled / disabled but only if the rel attribute is set to 'stylesheet' and not 'alternate stylesheet' - this defies the purpose of switching stylesheets, and goes against the W3C's HTML specification so I just accept that it will not handle the script correctly. ICEbrowser users will have to just stick to the default stylesheet, attepting to change it will leave them only with the persistent stylesheet.
- NetFront provides the collection but it is always empty ...
- Opera 7-8 and iCab 3 do not provide the collection at all.
Referencing the tags
The equivalent to document.styleSheets can be produced by referencing the relevant LINK and STYLE tags. The disabled property can be read and modified, and the title attribute is available. You will need to make sure that the link tags are being used to import stylesheets by checking their 'rel' attribute as link tags have many uses.
- Internet Explorer 4+ Win supports this technique, but until the disabled property has been set with JavaScript, it will show as false, even if the 'rel' attribute is set to 'alternate stylesheet' and hence the stylesheet is disabled.
- Internet Explorer 4+ Mac correctly supports this technique.
- Gecko (Netscape 6+, Mozilla etc.) correctly supports this technique.
- Opera 9+ correctly supports this technique.
- Konqueror and Safari/Chrome [KHTML/WebKit] support this technique, but until the disabled property has been set with JavaScript, it will show as false, even if the 'rel' attribute is set to 'alternate stylesheet' and hence the stylesheet is disabled.
- Opera 7-8 and iCab 3+ support this technique, but until the disabled property has been set with JavaScript, it will show as false, even if the 'rel' attribute is set to 'alternate stylesheet' and hence the stylesheet is disabled. Also, changes made using the view menu will not be reflected.
- ICEbrowser does not support this technique.
You can see that from these points, the following is required:
- Use the link and style tag technique in Opera 7-8 and iCab 3 and make sure that you do not try to store the user's preference until they have chosen a stylesheet.
- Either of:
- Use the link and style tag technique in Opera 9+.
- Use the document.stylesheets collection in Opera 9+.
- Use the link and style tag technique in KHTML/WebKit and make sure that you do not try to store the user's preference until they have chosen a stylesheet.
- Either of:
- Use the link and style tag technique in Gecko.
- Use the document.stylesheets collection in Gecko.
- Either of:
- Use the link and style tag technique in IE using document.all.tags to reference them in IE 4 and make sure that you do not try to store the user's preference until they have chosen a stylesheet.
- Use the document.stylesheets collection in IE, as this makes it easier to include IE 4 as well - make sure you use the title from the owningElement if the title is not immediately available.
- Use the document.stylesheets collection in ICEbrowser, but only bother if you are not using alternate stylesheets.
The W3C says the correct method is to use the styleSheets collection, but since that would only work in three browsers, and cause problems for others, I will use the link and style tag technique. I will add support for ICEbrowser, just in case they ever fix its handling, or in case you only want to switch preferred stylesheets.
The best way to check if the user has used the view menu is to check what stylesheets are enabled onload and onunload. If they are different, the user has chosen something from their view menu (this is not possible in Opera 7-8, as the disabled property does not reflect changes made in the view menu). Of course, you should also check if the changeStyle function has been run by making it store a variable to say so.
You may want to use the correct technique, but if you do, you will still need to do the link and style tag technique for
Opera 7-8 and iCab (using if(!document.styleSheets)
etc.), and you will need to do a browser detect to make KHTML/WebKit use the link and style
tag technique, and you will still need to make sure you do not store the preference in KHTML/WebKit, iCab and Opera 7-8 until the user has
chosen, and you will need to compensate for the lack of title in IE 4 Mac.
The script
Note, because getElementsByTagName returns an object with element 'index' and 'length' properties, and not a true collection, we cannot use the array.concat method to append the 'arrays' of link tags and style tags. Instead, we must cycle through the collections and add each element in turn.
function getAllSheets() {
//if you want ICEbrowser's limited support, do it this way
if( !window.ScriptEngine && navigator.__ice_version ) {
//IE errors if it sees navigator.__ice_version when a window is closing
//window.ScriptEngine hides it from that
return document.styleSheets; }
if( document.getElementsByTagName ) {
//DOM browsers - get link and style tags
var Lt = document.getElementsByTagName('link');
var St = document.getElementsByTagName('style');
} else if( document.styleSheets && document.all ) {
//not all browsers that supply document.all supply document.all.tags
//but those that do and can switch stylesheets will also provide
//document.styleSheets (checking for document.all.tags produces errors
//in IE [WHY?!], even though it does actually support it)
var Lt = document.all.tags('LINK'), St = document.all.tags('STYLE');
} else { return []; } //lesser browser - return a blank array
//for all link tags ...
for( var x = 0, os = []; Lt[x]; x++ ) {
//check for the rel attribute to see if it contains 'style'
if( Lt[x].rel ) { var rel = Lt[x].rel;
} else if( Lt[x].getAttribute ) { var rel = Lt[x].getAttribute('rel');
} else { var rel = ''; }
if( typeof( rel ) == 'string' && rel.toLowerCase().indexOf('style') + 1 ) {
//fill os with linked stylesheets
os[os.length] = Lt[x];
}
}
//include all style tags too and return the array
for( var x = 0; St[x]; x++ ) { os[os.length] = St[x]; } return os;
}
function changeStyle() {
for( var x = 0, ss = getAllSheets(); ss[x]; x++ ) {
//for each stylesheet ...
if( ss[x].title ) {
//disable the stylesheet if it is switchable
ss[x].disabled = true;
}
for( var y = 0; y < arguments.length; y++ ) {
//check each title ...
if( ss[x].title == arguments[y] ) {
//and re-enable the stylesheet if it has a chosen title
ss[x].disabled = false;
}
}
}
if( !ss.length ) { alert( 'Your browser cannot change stylesheets' ); }
}
...
eg.
<body onload="changeStyle('High Contrast','Big Font');">
Reduced, that becomes this:
function getAllSheets() {
if( !window.ScriptEngine && navigator.__ice_version ) { return document.styleSheets; }
if( document.getElementsByTagName ) { var Lt = document.getElementsByTagName('link'), St = document.getElementsByTagName('style');
} else if( document.styleSheets && document.all ) { var Lt = document.all.tags('LINK'), St = document.all.tags('STYLE');
} else { return []; } for( var x = 0, os = []; Lt[x]; x++ ) {
var rel = Lt[x].rel ? Lt[x].rel : Lt[x].getAttribute ? Lt[x].getAttribute('rel') : '';
if( typeof( rel ) == 'string' && rel.toLowerCase().indexOf('style') + 1 ) { os[os.length] = Lt[x]; }
} for( var x = 0; St[x]; x++ ) { os[os.length] = St[x]; } return os;
}
function changeStyle() {
for( var x = 0, ss = getAllSheets(); ss[x]; x++ ) {
if( ss[x].title ) { ss[x].disabled = true; }
for( var y = 0; y < arguments.length; y++ ) {
if( ss[x].title == arguments[y] ) { ss[x].disabled = false; }
} } }
Test it using the stylesheet switcher demo page.
See my stylesheet switching header file for a working example that can store the user's choice of stylesheet, even if they use the view menu in Gecko.
Last modified: 19 March 2011