Hello, I’m Maxime Euzière, JS developer for EQCSS.
In parallel to this documentation, let me suggest you to see the source code of EQCSS here: github.com/eqcss/eqcss/blob/gh-pages/EQCSS.js
You can find a lot of documentation about EQCSS’s history, usage and philosophy out there:
When we started this project, the requirements for EQCSS were:
Element Queriesallowing to style elements based on their size, their content or their state (most of the syntax proposals we could find online didn’t suit us)
What a great challenge!
After a lot of discussion and thinking, here’s how we organized the project and algorithm:
We have a global object EQCSS containing 2 functions:
EQCSS.load()
, called automatically when the DOM is ready. Its role is to find all the EQCSS code present in the page, gather it and parse it in order to extract all the useful parts. It can also be called manually if new EQCSS sources are added to the page after page load.EQCSS.apply()
, called automatically when the DOM is ready (after EQCSS.load), but also when the user interacts with the page. Its role is to test all the Element Queries parsed by EQCSS.load()
and apply their respective styles into the page if it is applicable. It’s called after resizeevents by default, but developers can also decide to call it after events like click, text input, scroll (like at the end of the test page), or manually as soon as the display needs to be updated.
Ex: apply EQCSS after keydown events:
window.addEventListener("keydown", function(){
EQCSS.apply();
});
EQCSS code can be written alongside regular CSS code. Its syntax @element (...) { ... }
is custom enough to stay ignored by browsers when they parse CSS. Although, it’s clean enough to be colored by syntax highlighters. Ex: codepen.io/tomhodgins/pen/yYVdBq
That’s why we allow to write EQCSS code directly in style
blocks (for embedded style) and link rel=stylesheet href=...
links (for external CSS files).
If you want to keep your EQCSS code separated from regular CSS, you can also include it in a script type="text/eqcss"
block (for embedded style) or script type="text/eqcss" src=...
includes (for external files)
EQCSS.load()
fetches all these elements into the page, and gather their content into a string: EQCSS.code
Embedded styles are read via innerHTML, external files are read via a synchronous AJAX request.
EQCSS.code
is then cleaned by a bunch of RegExps that remove all the comments, trim extra spaces and put each query on a separate line.
Before (HTML):
<style>
/* Hi */
@element ".class1" and (max-width: 40%){
/* HERE IT IS */
.class1 { background: yellow; }
}
/* BYE */
</style>
<script type="text/eqcss">
@element ".class2" and (min-width: 250px) and (max-width: 300px){
.class2 { background: yellow; }
}
</script>
After (JS):
EQCSS.code =
"@element ".class1" and (max-width: 40%){ .class1 { background: yellow; } }
@element ".class2" and (min-width: 250px) and (max-width: 300px){ .class2 { background: yellow; } }"
Then, another bunch of RegExps parse these lines and separate the selectors, conditions and styles into an object called EQCSS.data:
EQCSS.data = [
{
selector: ".class1",
conditions: [
{
measure: "max-width,
value: "40",
unit: "%"
}
],
style: ".class1 { background: yellow; }";
},
{
selector: ".class2",
conditions: [
{
measure: "min-width",
value: "250",
unit: "px"
},
{
measure: "max-width",
value: "300",
unit: "px"
}
],
style: ".class2 { background: yellow; }";
}
]
We now have a collection of EQCSS selectors, conditions and styles to apply. The first thing we can do with it is to query the page’s DOM to retrieve all the elements corresponding to the selectors, and mark them with a GUID
: a custom and unique attribute: data-eqcss-X-Y
.
X represents the current Element Query, and Y the current element matching this query.
The parent of each matched element is also marked with a custom attribute, or parent GUID
, in the form of data-eqcss-X-Y-parent
. Here’s an example:
Before:
<div>
<div class="class1"></div>
</div>
<div>
<div class="class1"></div>
<div class="class1 class2"></div>
<div class="class2"></div>
</div>
After:
<div data-eqcss-0-0-parent>
<div <div class="class1" data-eqcss-0-0></div>
</div>
<div data-eqcss-0-1-parent data-eqcss-0-2-parent data-eqcss-1-0-parent data-eqcss-1-1-parent>
<div class="class1" data-eqcss-0-1></div>
<div class="class1 class2" data-eqcss-0-2 data-eqcss-1-0></div>
<div class="class2" data-eqcss-1-1></div>
</div>
(NB: as you can see, an element can belong to multiple Element Queries (and thus, carry multiple GUIDs), and if multiple elements share a common parent, the parent will have multiple parent GUIDs.)
Then, a style
block is created in the page’s HEAD for each GUID (and the GUID becomes the id of the style
block).
Ex:
<head>
<style id="eqcss-0-0"></style>
<style id="eqcss-0-1"></style>
<style id="eqcss-0-2"></style>
<style id="eqcss-1-0"></style>
<style id="eqcss-1-1"></style>
</head>
(NB: These style blocks are created once, at the first call of EQCSS.apply()
. During the next calls, they are just reused if they already exist)
Then, for each query, and for each element matching the query, we have to test if all the query’s conditions are met.
We save the element’s computedStyle
as well as it’s parent’s computedStyle
, because they will be required to test the conditions.
A big JS switch allows to test each condition’s measure (ex: "min-width"
, "max-width"
, etc.) and compare it to the condition’s value and unit (ex: "40%"
, "250px"
).
All computed styles are defined by default in px, so to support other units (like %
, em
, vw
, vh
, etc.), we first need to convert these units into pixels. Depending on the unit chosen, this conversion requires the element’s own computedStyle
, or its parent’s, or the root element’s (html
).
There are also conditions that use units but not computedStyle
, like min(or max)-scroll-x(or y) that use the element’s scrollTop
and scrollLeft
instead.
Finally, some conditions (like min/max-children or min/max-characters) require no units and no computedStyle
at all. They just depend on the element’s content.
Anyway, if all the conditions of a query are met by an element, then EQCSS will fetch the style
block associated to this element (in the page’s head
) and fill it with the query’s css.
Ex: if the element [data-eqcss-1-0]
matches the query 1, then the head will contain this:
<head>
<style id="eqcss-0-0"></style>
<style id="eqcss-0-1"></style>
<style id="eqcss-0-2"></style>
<style id="eqcss-1-0">.class2 { background: yellow; }</style>
<style id="eqcss-1-1"></style>
</head>
And the browser will instantly apply this style on all the page.
At the contrary, if all the conditions are NOT met, the style
block is emptied and the corresponding style disappears into the page.
Special keywords are handled like this:
:self
into the query. The trick here is to replace :self
with [data-eqcss_X_Y]
before copying the css code into the style
block. This produced a CSS selector that matches only the element having the current GUID, i.e. the current element, that we want to call :self
.:parent
is quite similar: EQCSS replaces it with [data-eqcss-X-Y-parent]
in the CSS code before applying it.$root
is replaced by html
eval("abcd")
or eval("oneFunction()")
, it evaluates the expression inside (in JavaScript) the parenthesis and replaces it with the value returned. Ex: if abcd = 40
, width: eval("abcd")px
becomes width: 40px
.Many polyfills were included into EQCSS to allow a good compatibility between browsers:
domready(func)
: calls the function passed in parameter as soon as the DOM is ready (it happens before onLoad)EQCSS-polyfills.js
also contains polyfills specific for IE < 9:addEventListener()
getComputedStyle()
/ getPropertyValue()
document.querySelector()
and querySelectorAll()
(just a basic version that works with ids, classes and tag names)element.textcontent