JavaScript is in this very special place when it comes to web technologies. It either provides functionalities that can't be found anywhere else or can easily replace other web languages - HTML and CSS that is.
While most JS developers probably know of the DOM API and all the UI libraries and frameworks built on top of it, the knowledge of "CSS API" (it's not technically called that way, but you get the point), is less common.
I've already covered the API you can use to create your CSS stylesheets right from JS in my previous article. Instead, today I'd like to focus on something more advanced - on how to control your @media
CSS rules - you've guessed it - in JS!
CSS API recap
Let's start with a very quick recap of the previous article.
You can access a stylesheet in 2 ways - either through the sheet
property of a <style>
tag DOM element or as one of the document.styleSheets
indexed collection's items. In both cases, the result is an object implementing the CSSStyleSheet
interface that then gives you further access to control methods like insertRule()
and removeRule()
, as well as properties like cssRules
.
const style = document.createElement("style");
document.head.appendChild(style);
const styleSheet = style.sheet;
const ruleIndex = styleSheet.insertRule(".example { background-color: red }");
const rule = styleSheet.cssRules[ruleIndex];
The insertRule()
method returns the index at which the new CSS rule was inserted. This can then be used to access the rule object implementing the CSSRule
interface. And such an object, as expected, has some properties of its own - mainly used to configure and access the rule's data.
CSSRule
Now, here's where we slow down a little bit. That's because the CSSRule
and its derivatives must be well-understood, to be able to create more complex JS-based stylesheets.
On its own - although you'll never see it that way - CSSRule
has only a few properties. The most important ones are probably the cssText
- holding your rule's textual CSS representation and the type
- a constant value indicating the type of the given CSSRule
.
// ...
rule.cssText; // ".example { background-color: red; }"
There are multiple types and thus derivatives of the CSSRule
interface. The most common one - CSSStyleRule
is responsible for rules like the one you see above. In addition to standard CSSRule
properties, it also has some more interesting ones like selectorText
- textual representation of the rule's CSS selector, and style
- a CSSStyleDeclaration
object, just like DOM element's inline styles you might be accustomed to.
// ...
rule.selectorText; // ".example"
rule.style.backgroundColor; // "red"
rule.style.backgroundColor = "green";
rule.style.backgroundColor; // "green"
As a nice bonus - did you know that you can change the style
of your rule, altering it and all the elements it's applied to in real-time!?
CSSMediaRule
But all the different CSSRule
s are not what you came here for - no. You've come for the CSS @media
rule. And, as one might expect, it also has its reflection on the JavaScript side - this time in the form of the CSSMediaRule
.
CSSMediaRule
is that much interesting because of its deeper inheritance. Unlike simple CSSStyleRule
that's a direct child of the CSSRule
, CSSMediaRule
additionally has CSSGroupingRule
and CSSConditionRule
(in the given order) as its parents.
This says a lot about the rule. The CSSGroupingRule
is meant for the rules that contain nested rules within them, while CSSConditionRule
means that they're applied only when a certain condition is met. Remember CSS syntax for a @media
rule?
@media screen and (min-width: 900px) {
.example {
background-color: blue;
}
}
Now, both of the CSSMediaRule
parents add important properties and methods to it. Going from all the way up (directly below CSSRule
itself) the CSSGroupingRule
adds methods like insertRule()
and deleteRule()
as well as cssRules
property to the party. Sounds familiar? That's because these are similar functionalities to what we saw earlier, all the very beginning with the CSSStyleSheet
interface.
// ...
const mediaRuleText = `@media screen and (min-width: 900px) {
.example {
background-color: blue;
}
}`;
const mediaRuleIndex = styleSheet.insertRule(ruleText);
const mediaRule = styleSheet.cssRules[mediaRuleIndex];
mediaRule.cssRules[0].selectorText; // ".example"
mediaRule.cssRules[0].style.backgroundColor; // "blue"
In our case, there's only 1 rule grouped by the CSSGroupingRule
- a simple CSSStyleRule
, which means that we've come a full circle.
Next up, we've got the CSSConditionRule
which brings with it the conditionText
property. This guy allows us to access the textual representation of the CSS condition. In our case it's:
mediaRule.conditionText; // "screen and (min-width: 900px)"
The CSSMediaRule
also adds a property of its own - media
- that's equal to an object implementing MediaList
interface. Basically, a bit more advanced version of conditionText
. It's not really important for anything so if you're interested, just check out the MDN docs.
The other way around
So, that pretty much wraps it up for the CSSMediaRule
and related APIs. There are quite a few variations of CSSRule
like this one, which when used together can lead to pretty impressive results. Dynamic, manageable CSS-in-JS libraries like my own Prototope with complex real-time updates are definitely possible.
But you might also say that these things are best for the CSS to deal with. And you'd be absolutely right - that's what CSS was designed for. But if so, maybe you'd be interested in something different?
What if I told you that there's a way to evaluate media queries right in JS? To know when e.g. a window has the desired width or height? Well, that's surely possible and all thanks to matchMedia()
matchMedia
So, matchMedia()
is a method accessible directly on the window
object (globally), that allows you to parse given media query and react to the changes in its activity.
const mediaQuery = matchMedia("screen and (min-width: 900px)");
matchMedia()
returns what's called a MediaQueryList
object. This guy provides you with everything you'd want when working with media queries. You've got the most important matches
property to check whether the media query matches the current website's state, the media
property to get back the provided media query string, and two addListener()
and removeListener()
methods to listen for the media query state changes.
mediaQuery.addListener(() => {
mediaQuery.matches; // true or false
});
mediaQuery.media; // "screen and (min-width: 900px)"
Now, you cannot argue with the usefulness of this feature. Being able to check whether the certain media query applies is extremely helpful when dealing with any kind of JS-driven UI - take Masonry Grid for example. The matchMedia()
way is much faster than any other similar solution (especially the one with constant resize
even monitoring). And have I already said that it has great cross-browser support with up (or rather down) to IE 10!
Conclusion
With the CSS API and matchMedia()
I think I've shown you an impressive side of JavaScript capabilities. I hope you've learned something new and will now be able to create all sorts of JS wonders - from simple JS-driven UI layouts to full-blown CSS-in-JS libraries.
For more web development guides and tutorials, follow me on Twitter, Facebook, or right here on Dev.to. I've also got a YouTube channel (not very active recently, but I'm working on it), which you might want to check out and subscribe to. Thanks for reading this piece and I wish you happy coding!
Top comments (1)
Thanks for the article.
PS: It is called CSSOM.