views:

146

answers:

6

Is there a way to add css from a string in the javascript file to the head of a document with javascript?

Let's say we have a webpage, which has a lightbox script, this script requires a css file to function.

Now adding this css file with <link> will make the css file download even for people that don't have js enabled.

I know that I can dynamically load the css file with the script, but that also means that there will be 2 http requests, and in cases where there is little to no css in the file I find this inefficient.

So I thought to myself, what if you could put the css that you have in the css file, into the script, have the script parse the css and add it into the head, or even better just have the script add the css directly into the <head> of the document.

But I have found nothing online that suggests that this is possible, so is it possible to add css to the head with js?

edit:

I edited roryf's answer to work cross browser (except IE5)

Javascript:

 function addcss(css){
    var head = document.getElementsByTagName('head')[0];
    var styleElement = document.createElement('style');
    styleElement.setAttribute('type', 'text/css');
    if (styleElement.styleSheet) {   // IE
        styleElement.styleSheet.cssText = css;
    } else {                // the world
        styleElement.appendChild(document.createTextNode(css));
    }
    head.appendChild(styleElement);
 }
A: 

You can try http://furf.com/getCSS/

Júlio Santos
@Júlio, he specifically asks for a way that avoids loading a new file
Gaby
+2  A: 

Have a look at the following link ..

It explains every little details about your question ...

and it is named appropriately Totally Pwn CSS with Javascript

Gaby
+1 thanks for the link
YuriKolovsky
A: 

You could use the jQuery library to select your head element and append HTML to it, in a manner like:

$('head').append('<link rel="stylesheet" href="style2.css" type="text/css" />');

You can find a complete tutorial for this problem here

Using jQuery is very easy and I suggest using it, in the case you're not already are.

Anzeo
Again, this method loads a new file, which Yuri wanted to avoid. Also, loading the entire jQuery library for something as simple as this is rather silly.
TRiG
+3  A: 

If you don't want to rely on a javascript library, you can use document.write() to spit out the required css, wrapped in style tags, straight into the document head:

<head>
  <script type="text/javascript">
    document.write("<style>body { background-color:#000 }</style>");
  </script>
  # other stuff..
</head>

This way you avoid firing an extra HTTP request.

There are other solutions that have been suggested / added / removed, but I don't see any point in overcomplicating something that already works fine cross-browser. Good luck!

http://jsbin.com/oqede3/edit

Jeriko
That's fine until you want to add a CSS rule after the document has rendered.
Tim Down
Good thing OP doesn't have that requirement then! Your solution covers that, but imho it's totally overkill for non-js/lightbox css degradation as this.
Jeriko
Yes, you're probably right.
Tim Down
One thing to note: `document.write` doesn't work in real XHTML documents, but that's unlikely to be a problem since hardly anyone really uses XHTML.
Tim Down
it's interesting that it works but having style in the body looks a bit hacky :) p.s. you can put the document.write in the head section of the site and it seems to work fine.
YuriKolovsky
Yes, `document.write` will work fine in the `<head>`.
Tim Down
My bad - it doesn't fire when the `script` is nested inside `style` tags, but you can drop it straight into the head :)
Jeriko
@Jeriko you can edit the answer so that you don't confuse people
YuriKolovsky
Ewww... Since several issues I've had with `document.write` destroying my entire page, I make a point to avoid it and stick to `document.getElementById(...).innerHTML = `. There's never a need for `document.write`.
steven_desu
Updated my answer to include `getElementById`. I don't like `document.write` either, but it's often a lot simpler to explain, as you can see.
Jeriko
@steven_desu I added a non document.write solution in my question. plus innerHTML ain't that much better :) @Jeriko I really don't like your dynamic styles approach, why not use the edited roryf solution? MUCH more elegant IMO. p.s. I marked your's as best because it was the simplest and working solution to my incomplete question.
YuriKolovsky
I tend to prefer solutions that are most likely to work cross-browser. It seems @roryf's answer has IE issues. As far as I'm concerned, when you're already doing something quick and dirty (adding css rules on the fly), it's whatever gets the job done properly :P
Jeriko
@Jeriko: assigning to the `innerHTML` property of a `<style>` element doesn't work in IE 6-8 (not sure about 9). Stick to the `document.write` solution and don't worry about @steven_desu's comment: there are some tasks for which only `document.write` will do.
Tim Down
@steven_desu: `document.write` will only destroy your entire page if you use it incorrectly (e.g. after the page has rendered). There's no danger if you know what you're doing. And as this very example proves, there sometimes is a need for `document.write`.
Tim Down
@jeriko yea, I agree with Tim Down, consider removing the second example, or make it into what I posted in the question which is roryf's answer that works in IE.
YuriKolovsky
Thanks Tim and Yuri - seems we are in agreeance.. I never understood why things had to be overcomplicated when a simple solution works, and invariably works better :) I've removed the other solution.
Jeriko
+1  A: 

Here's a function that will dynamically create a CSS rule in all major browsers. createCssRule takes a selector (e.g. "p.purpleText"), a rule (e.g. "color: purple;") and optionally a Document (the current document is used by default):

var addRule;

if (typeof document.styleSheets != "undefined" && document.styleSheets) {
    addRule = function(selector, rule) {
        var styleSheets = document.styleSheets, styleSheet;
        if (styleSheets && styleSheets.length) {
            styleSheet = styleSheets[styleSheets.length - 1];
            if (styleSheet.addRule) {
                styleSheet.addRule(selector, rule)
            } else if (typeof styleSheet.cssText == "string") {
                styleSheet.cssText = selector + " {" + rule + "}";
            } else if (styleSheet.insertRule && styleSheet.cssRules) {
                styleSheet.insertRule(selector + " {" + rule + "}", styleSheet.cssRules.length);
            }
        }
    }
} else {
    addRule = function(selector, rule, el, doc) {
        el.appendChild(doc.createTextNode(selector + " {" + rule + "}"));
    };
}

function createCssRule(selector, rule, doc) {
    doc = doc || document;
    var head = doc.getElementsByTagName("head")[0];
    if (head && addRule) {
        var styleEl = doc.createElement("style");
        styleEl.type = "text/css";
        styleEl.media = "screen";
        head.appendChild(styleEl);
        addRule(selector, rule, styleEl, doc);
        styleEl = null;
    }
};

createCssRule("body", "background-color: purple;");
Tim Down
Very thorough answer, and I believe it will come in handy, but it will be painstaking to take a simple css stylesheet and convert it to this.
YuriKolovsky
Yes, you're right. I'd use @Jeriko's answer if I were you.
Tim Down
+1  A: 

A simple non-jQuery solution, albeit with a bit of a hack for IE:

var css = ".lightbox { width: 400px; height: 400px; border: 1px solid #333}";

var htmlDiv = document.createElement('div');
htmlDiv.innerHTML = '<p>foo</p><style>' + css + '</style>';
document.getElementsByTagName('head')[0].appendChild(htmlDiv.childNodes[1]);

It seems IE does not allow setting innerText, innerHTML or using appendChild on style elements. Here is a bug report which demonstrates this, although I think it identifies the problem incorrectly. The workaround above is from the comments on the bug report and has been tested in IE6 and IE9.

Whether you use this, document.write or a more complex solution will really depend on your situation.

roryf
Doesn't work in IE. Certainly not 6 or 7, don't have 8 available to test right now. IE doesn't like `document.createElement('style')`.
Tim Down
@Tim works for me in IE6 and 9, since your solution has the exact same line perhaps you can explain further
roryf
@roryf: Oh, yes, you're right, sorry. Foolishly I made the elementary mistake of believing the line number in IE's error. The error's actually in `styleElement.innerHTML = css;`. Certainly fails in IE 7, retesting in IE 6 now (waiting for VM to come alive).
Tim Down
@roryf: Same error in IE 6.
Tim Down
@Tim thanks, I suspected that might be a problem, shame on me for not testing properly!
roryf
so now it works in all browsers?
YuriKolovsky
@YuriKolovsky: No, it doesn't work in IE 6-8.
Tim Down
http://www.phpied.com/dynamic-script-and-style-elements-in-ie/ a link that i found thanks to the ie error that it gave me :)
YuriKolovsky
if this worked cross-browser and maybe even ie5, then this would be the best answer ever.
YuriKolovsky
I couldn't leave this alone so did a bit of research and found a workaround for IE, this should now work cross-browser
roryf
@roryf: So you have. Eugh. `<style>` element is not a valid descendant of a `<div>` element, so it may be possible that future, more standards-compliant IEs may not allow this. But still, inventively horrible. Well done :)
Tim Down
hahahaha, good job!
YuriKolovsky
@Tim good point, wonder if creating a `<head>` element would work... Anyway, 'eugh' is a pretty accurate description ;-)
roryf