“There are very few things that surprise me.” – Judi Dench
Myself, I would not go as far as Dench – at least not in the world of Web development. However, I will say that it isn’t everyday that I stumble upon something truly new (well to me) and delightful. Nonetheless, not too long ago I discovered that it is possible to change external CSS style sheets for a running web page via JavaScript – i.e. dynamically, at will, after the page has loaded. I confess I felt sheepish that I had not known this was possible. Perhaps you didn’t know it either. Read on if you wish and you’ll find out how.
Why is this ability to dynamically swap external style sheets useful? There are at least three reasons:
- In cases where a large number of individual CSS/style changes need to be applied all at once, this style sheet swap method might be faster to execute and easier to maintain.
- The second reason is perhaps a specialized case of the general case stated above – this is a great way to implement dynamic theme changes that can be invoked by the user at any time.
- The third reason is that this technique can assist with responsive web site design – i.e. changing the layout and sizes of element in a page to accommodate radically different screen sizes. A more standard approach to responsive design relies on CSS media queries, which allows CSS to alter itself to accommodate different screen sizes. Unfortunately, the sad reality of today is that many of the older mobile platforms (i.e. device, plus OS, plus mobile browser) do not properly support CSS queries. Even on newer platforms, media query support can be quirky. So one alternative approach is to substitute the technique being described in this article to allow for “responsive” CSS changes. Instead of using media queries, JavaScript code can check the browser document size upon initial display and every refresh and then dynamically adjust font sizes, and specific DOM element dimensions by swapping in style sheets that have been created for best viewing at certain trigger sizes.
Actually accomplishing this feat is easier than one might expect – at the heart of it, there are only three standard, cross-browser DOM functions that need be employed for this purpose – removeChild, createElement and appendChild. The basic idea is to remove one link element from the header of a page and add a new link element to replace it. This methodology requires that two (or more) CSS files must already exist – no new CSS file is being created using this technique. There are ways to dynamically create individual style sheet rules but this article does not cover that topic. In any event – in my opinion – dynamically swapping entire style sheets is more useful.
To demonstrate actual swapping of external style sheets at run time I have built at a working demo page. I will show every line of code and annotate how it works in the succeeding paragraphs, but it may be desirable to run the demo first. Here is a link to the demo at my main site, uberiquity.com. Check it out first or wait until after the explanation. I’ll provide another link at the end of the article.
The demo consists of seven small files:
- changecss.html
- changecss.js
- base.css
- mono.css
- red.css
- green.css
- blue.css
Only two of these files contain code and only one file, changecss.js, contains the actual code that enables the swap. The base.css file contains all the non-swappable CSS used by the HTML for the demo, mostly formatting information that is non-color oriented – e.g. setting the sizes of things. The other four CSS files represent themes. It is these files that we can swap to change the theme of the demo page on the fly. We’ll look at changecss.js first, then we’ll see how the single function in changecss.html calls the methods therein.
I like the names of functions and variables to be self-explanatory. This means that to some people my function names may be annoyingly long. I realize that this is a bit of a “religious thing” and not everyone likes the verbose approach – please forgive me if it rubs you the wrong way. In any event, I list the content of the changecss.js file below. It contains only two functions.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
///// changecss.js ///// function removeExternalStyleSheetLink(cssLinkId) { var cssLink = document.getElementById(cssLinkId); cssLink.parentNode.removeChild(cssLink); } function createExternalStyleSheetLink(cssLinkId, href) { var cssLink = document.createElement("link"); cssLink.id = cssLinkId; cssLink.type = "text/css"; cssLink.rel = "stylesheet"; cssLink.href = href; cssLink.media = "screen"; document.getElementsByTagName("head")[0].appendChild(cssLink); } |
To make the swap, we need to first remove the existing external style sheet link. For this purpose, we call upon the removeExternalStyleSheetLink function. When first loaded, the demo page is linked to the theme CSS file named mono.css. However, the demo user is allowed to swap in any of the other theme CSS files. In order for this to happen, the demo first calls upon the removeExternalStyleSheetLink function to remove the link to mono.css, i.e.: <link id=”mono” rel=”stylesheet” type=”text/css” href=”mono.css” media=”screen” />. Notice the rather unusual fact that the link has an id attribute – this is perfectly legal HTML (it’s just not done very often). This id value of “mono” is the value that must be passed in the cssLinkId argument to the function. The function uses it to get a reference to the DOM object of the external style sheet link that must be removed, and then does the removal by telling the parent of this DOM object to “remove me”.
Once the link to the original CSS theme file is gone, a new link element must be inserted into the head of the document to complete the swap. For this purpose, we call upon the second function, createExternalStyleSheetLink. The job of this function is to create a DOM object that will represent a link to the CSS file that is to be “swapped in”. Like the removal function, the create function takes an id as an argument, because it is important to give this new link tag an id. Giving it an id attribute makes it easy to reference it in case it too will be swapped out at some future time. The second argument, href, will of course contain the name of the CSS file that will be linked to (i.e. swapped in). The function uses the standard DOM manipulation JavaScript functions createElement and appendChild to perform the work of creating the link object in memory and then inserting it into the proper place in the DOM. The proper place is in the head of the document, as denoted by the invocation: document.getElementsByTagName(“head”)[0].
Now let’s take a quick look at changecss.html. I’ll list the entire file but the the main point of interest is the solo function named changeStyleSheet. Most of the rest of what is there merely exists to provide objects whose colors can be changed by a theme switch (i.e. proof of concept). Note that I don’t necessarily advocate defining functions in HTML pages, but in this case, the function is quick and dirty and relies upon embedded page knowledge. I’m doing a couple of other naughty things here, for instance, using a non-namespaced global variable. Please bear in mind that I am striving for brevity and clarity of presentation – not necessarily trying to use best practices.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
<!-- changecss.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="pragma" content="no-cache"> <link rel="stylesheet" type="text/css" href="base.css" media="screen" /> <link id="mono" rel="stylesheet" type="text/css" href="mono.css" media="screen" /> <script type="text/javascript" src="changecss.js"></script> <script type="text/javascript"> var gCurThemeName = "mono"; function changeStyleSheet(newThemeName) { removeExternalStyleSheetLink(gCurThemeName); createExternalStyleSheetLink(newThemeName, newThemeName + ".css"); gCurThemeName = newThemeName; document.getElementById("using").innerHTML = gCurThemeName + ".css"; } </script> </head> <body> <div id="titlebar">Dynamically Change CSS</div> <p> currently using: <span id="using">mono.css</span> </p> <div id="spacer"> <span id="buttons"> <input type="button" value="Apply Red Theme" onclick="changeStyleSheet('red')" /> <input type="button" value="Apply Green Theme" onclick="changeStyleSheet('green')" /> <input type="button" value="Apply Blue Theme" onclick="changeStyleSheet('blue')" /> </span> </div> </body> </html> |
Before we look at the changeStyleSheet function, note again the link tag containing the id of “mono”. That is the default theme that may be swapped out by pressing either the “Apply Red Theme”, “Apply Green Theme” or “Apply Blue Theme” buttons. It is worth noting that the act of swapping the CSS files causes the page to radically change it’s appearance, but this does not cause a page refresh.
Because the functions in changecss.js have already been covered, there is nothing mysterious about the changeStyleSheet function. We already understand how the removeExternalStyleSheetLink and removeExternalStyleSheetLink functions work, and the changecss.js function does little more than simply call them in succession. The remainder of what the function does revolves around remembering the name of the CSS file for the current theme and displaying that information to the user.
The only remaining files to discuss are the CSS files – here is base.css. This file never gets swapped out – which is of course the normal scenario for CSS files. It performs a useful function in the demo by providing CSS that never changes, but its existence in the demo proves something as well. The technique presented here is perfectly adept at “cherry picking” links – base.css remains undisturbed now matter how many times one swaps themes in the demo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/* base.css */ body { margin: 0; padding: 0; background-color: lightgray; text-align: center; } #titlebar { padding: 20px; font-size: 40px; color: white; } #using { margin-left: 5px; padding: 0 5px 2px 5px; background-color: white; font-weight: bold; } #spacer { margin-top: 80px; } #buttons { border-style: solid; border-width: 2px; padding: 10px; background-color: white; } |
What follows after this paragraph are the listings of the four CSS files that could be considered to be “themes”. There is nothing special at all about the makeup or structure of these files – they are just standard CSS files. It is the JavaScript in changecss.js and the browser’s CSS engine itself that provide all the magic.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/* mono.css */ body { background-color: lightgray; } #titlebar { background-color: black; } #using { color: black; } #buttons { border-color: black; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/* red.css */ body { background-color: #FF7373; } #titlebar { background-color: #A00600; } #using { color: #A00600; } #buttons { border-color: #A00600; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/* green.css */ body { background-color: #73FF73; } #titlebar { background-color: #00A600; } #using { color: #00A600; } #buttons { border-color: #00A600; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/* blue.css */ body { background-color: #7373FF; } #titlebar { background-color: #0000A6; } #using { color: #0000A6; } #buttons { border-color: #0000A6; } |
And there you have it – simple, sweet and powerful. Here is that repeat link to the demo I promised I would provide. If you haven’t yet done so, take a moment to play with it.