I have something like:
const someCSS = `
.foo {
padding: 20px;
background-color: #ddf;
width: 100px;
}
.bar {
height: 100px;
}
.foo {
padding-top: 30px; /* this overrides the previous one */
}
`;
I can add this do the DOM and get back all selectors with each rule like this (jsFiddle):
const style = document.createElement('style');
style.innerHTML = someCSS;
document.head.append(style);
const styleSheet = Array.from(document.styleSheets).find(ss => ss.ownerNode == style);
const rules = Array.from(styleSheet.rules).map(rule => rule.cssText);
function styleToObject(rules, mergeWith = {}) {
return [...rules].reduce(
(obj, rule) => (obj[rule] = rules[rule], obj), mergeWith
);
}
const styleObject = Array.from(styleSheet.rules).reduce(
(obj, rule) => (obj[rule.selectorText] = styleToObject(rule.style, obj[rule.selectorText]), obj), {}
);
document.querySelector('pre').appendChild(
document.createTextNode(JSON.stringify(styleObject, null, '\t'))
);
and get something like this:
{
".foo": {
"padding-top": "30px",
"padding-right": "20px",
"padding-bottom": "20px",
"padding-left": "20px",
"background-color": "rgb(221, 221, 255)",
"width": "100px"
},
".bar": {
"height": "100px"
}
}
Is there another way to have the browser do this, without touching the DOM? I mean have a CSS text parsed by the browser (no regex) without actually styling anything in the page.
I though about adding it to a iFrame, but before its appended to the DOM the document is not available...
The CSS parser takes the bytes and converts them into characters, then tokens, then nodes and finally they are linked into the CSSOM. The browser does something called selector machting which means that each set of styles will be matched against all nodes (elements) on the page.
The CSS parser has to read nested selectors from right-to-left in order to guarantee that they end up underneath the correct nodes. Turning CSS into the CSSOM is considered to be a “render-blocking” stage just like building the DOM out of our HTML.
The short answer is no, you can't without changing the DOM.
If your concern is the added element triggering a redraw or the loaded style influencing the page in any way, you could add a "never-matching" media rule to the <style>
-element you create.
For example:
style.setAttribute('media', '(max-width: 0)');
Working fiddle
EDIT Was working on an example utilising this trick/hack/solution, you can find it here. Only now noticed the update to the question which is rather similar in mechanics (although my sample will work in less green browsers (not part of the question, I know)).
I've checked some sources which I've come across when I was trying to do a similar thing, most notably MDN - CSSStylesheets is very thorough and states:
A CSSStyleSheet object is created and inserted into the document's styleSheets list automatically by the browser, when a style sheet is loaded for a document. As the document.styleSheets list cannot be modified directly, there's no useful way to create a new CSSStyleSheet object manually (although Constructable Stylesheet Objects might get added to the Web APIs at some point). To create a new stylesheet, insert a or element into the document.
(Emphasis mine. CSO already mentioned by @Ouroborus)
I haven't done a lot of testing on various browsers, but I haven't seen redraws and/or reflows by adding the (media queried) style node to the <head>
, unlike adding an <iframe>
.
I'm curious if someone out here knows of a solution which relies on the (cross-)browser for processing CSS without hitting the DOM, as I haven't found it and ended up building a Tokenizer/Lexer to create such a tree).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With