loading...
Cover image for CSS @media rule in JavaScript

CSS @media rule in JavaScript

areknawo profile image Arek Nawo Originally published at areknawo.com ・6 min read

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 CSSRules 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!

Buy Me A Coffee

Posted on by:

areknawo profile

Arek Nawo

@areknawo

Hobbyist. Web Developer. 👨‍💻 Freelancer. Blogger. Making awesome websites. 😍

Discussion

markdown guide
 

Thanks for the article.

PS: It is called CSSOM.