Skip to content

Instantly share code, notes, and snippets.

@BriceShatzer
Last active May 12, 2016 20:10
Show Gist options
  • Save BriceShatzer/3383e4cbc251e959bcd3 to your computer and use it in GitHub Desktop.
Save BriceShatzer/3383e4cbc251e959bcd3 to your computer and use it in GitHub Desktop.

##Auditing media queries using JavaScript

TL;DR: You can use JavaScript to easily audit the media queries that are present in your project. This could potentially provide insight into portions of your styling that might need to be examined more closely. Don't care about the rational behind doing something like this and are just looking for a snippet? Head here.

While the overall thoughtfulness around how frontends are built has improved in recent years, generally speaking, styling simply isn't treated with the same amount of care and consideration as other parts of the front-end stack. Marry this apathy with tools like Sass/LESS/Stylus which enable developers to easily write complex and powerful CSS, and it's easy to envision the sort of tangled, nightmarish abomination that might exist in this world.

In my experience, this usually takes the form of a framework like bootstrap or foundation that has had so many layers of paint and ad hoc fixes applied to it that Theseus would be proud.

The likelihood of confronting this sort of monstrosity is never higher then when a developer inherits an existing web project, and on the surface, poorly written styling might not seem like a big issue. Unfortunately, when the time comes to make updates to the project, the flaws and idiosyncrasies that exist in the CSS can quickly become a massive time sink as ad hoc fixes for one problem cascades into other issues. The ideal course of action would be to go through and completely refactor the styling, but due to time constraints and the perceived magnitude of the task, it's something that is rarely is attempted.

One solution to this apprenhension is to audit the style sheet's media queries. This sort of analysis is an easy way to see where potential issues and low-hanging fruit might exist. This provides a clear starting place for any refactoring work and helps make a seemingly herculean task into something much more palatable. Best of all, this discovery and documentation process can be automated using JavaScript.

####What are we doing

So what exactly will be happening?

  • Pick a target style sheet and get it's cssRuleList
    Note: Keep in mind that style sheets are subject to the same-origin policy. This means that if the style sheet's origin doesn't match that of the page (e.g. being loaded from a CDN), the cssRules property of the CSSStyleSheet object isn't available and returns null. Some thoughts on dealing with this issue can be found here.
  • loop through this collection of CSSRules looking for CSSMediaRules
  • When one is found,
    • if it hasn't been seen before, map it to mediaQueriesMap variable we have setup,
    • increment the count value for this CSSMediaRule in the map,
    • add all cssRules found within this particular instance of the media query to the entry in the mediaQueriesMap variable,
  • Finally we format mediaQueriesMap into a table and print it in the console.

Code

var mediaQueriesMap = new Map();

var rules;


//--isolate the style sheet you want to work with. 

//set it directly by picking it from the styleSheets array. Here we're using the sheet at index 3.
rules = document.styleSheets[3].cssRules;

//set it by referencing it's url
function getStyleSheetByUrl(url){
    for (var i = document.styleSheets.length - 1; i >= 0; i--) {
        if( document.styleSheets[i].href == url){
            rules = document.styleSheets[i].cssRules;
        }
    }
}
getStyleSheetByUrl('http://www.webste.com/style.css');


//--create the map 
Array.prototype.forEach.call(rules, function(rule,i){
    if(rule instanceof CSSMediaRule){
        var mediaQueryRule = rule.media.mediaText;
        if(!mediaQueriesMap.has(mediaQueryRule)){//adds rule to the map if missing
            mediaQueriesMap.set(mediaQueryRule, {styleRules:[], occurrences:0});
        }
        //log the rule's occurance
        mediaQueriesMap.get(mediaQueryRule).occurrences++;
        //add this media queries style rules to the map
        Array.prototype.forEach.call(rule.cssRules, function(styleRule,i){        
            mediaQueriesMap.get(mediaQueryRule).styleRules.push(styleRule);        
        });
    }
});

//--print results to a console table 
var reportingTable = []
for (var entry of mediaQueriesMap){
    reportingTable.push( {
        mediaQuery:entry[0], 
        occurrencesInStylesheet:entry[1].occurrences,
        countOfStyleRules:entry[1].styleRules.length
    });
}

console.table(reportingTable);


//optional - print results to a csv formated string that can be saved via a text editor
/*
var csv = '--Media Query--,--times it appears--,--style rules within this media rule--\n';
for (var entry of mediaQueriesMap){ 
    csv+=entry[0]+', '+entry[1].occurrences+', '+entry[1].styleRules.length+'\n'
}
console.log(csv);
*/

####Analyzing the results

What do we do with this data? The general idea is to look for things that that seem different or stand out. Developers are creatures of habit, so if something seems inconsistent, peculiar, or out-of-place, it's likely that it was written by a different developer, or under different circumstances. These are the places we want to initially focus on, as they are more likely yield potential refactoring opportunities. At the very least they can be brought into alignment with the rest of the codebase to help make everything more maintainable going forward.

Specifically, we're on the looking for things like:

  • inconsistent or mistyped values
  • weird edge cases & one-offs that could potentially need investigating.
  • potential definition overlaps and places to check for unintentional "double styling"

In the example below, I've run the audit script on a project that I recently inherited.

At the top of table we see several media queries that contain the majority of the rules for this project. We know that this project was built uing Foundation 5, so seeing their media feature definitions using the em length units is to be expected. These media queries are most likely using Foundation's built in variables or are defined using a value that is consistent with those variables.

Around #14 ("screen and (max-width: 1010px)) we see the media queries begin to be defined using the absolute length unit of px.
While arguments can be made for using either length unit (px or em) in media query definitions, the bigger issue in this particular instance is of consistency. With the vast majority of the styling rules being housed in em based media queries, these px based definitions stand out, and should be examined more closely for code smell, adherence/disregard for best practices, and broken code. At the very least, they should be rewritten to use the established methodology of the rest of the project.

The next point of interest involves #17 ( "screen and (max-width: 770px)" ). Beyond the previously mentioned length unit inconsistencies, their doesn't seem to be anything inherently wrong with this particular media query at first glance. The issue starts to become more apparent when it is compared against some of the other media queries. #16 ( "screen and (max-width: 768px) and (min-width: 320px)" ) and #19 ( "screen and (max-width: 768px)" ) are using incredibly similar (but not quite matching) values in their definitions. The odds of a specific rule needing to be exactly 2px smaller than an established breakpoint isn't very high. The more likely explanation is that the author either forgot or wasn't aware of the exact breakpoint that is used elsewhere in the project. Either way, this portion of the code is probably something that should be flagged for a closer look.

Finally, #20 ( "screen and (min-width: 768px)" ) appears to be overlapping with some media queries that are set to use "max-width: 768px" ( #16 & #19). It's no accident that CSS frameworks define their breakpoints in a consistent manor that prevents overlap (e.g. Bootstrap & Foundation), so when there are multiple definitions checking a particular media feature for an identical value but with opposing prefixes, it is something that should be investigated. Perhaps it's just a simple inconsistency in how a particular breakpoint was authored, but there is also a possibility of a huge issue where multiple elements are getting conflicting styling instructions when the width is 768px.

Expanding the usage

The mediaQueriesMap map that is created is fairly flexible. In addition to creating a general overview, it can be used to drill into specific media queries. In the prior example we can see that the media query screen and (max-width: 1000px) is being used in the style sheet 13 different times to declare 13 different rules. Using our existing mediaQueriesMap variable we can see what selectors are being used for each of those rules.

var rules_at_this_breakpoint = mediaQueriesMap.get("screen and (max-width: 1000px)").styleRules


Array.prototype.forEach.call(rules_at_this_breakpoint, function(rule){
    console.log(rule.selectorText);
});

The resulting output could be useful in tracking down where exactly within the a project's uncompiled Sass/LESS/Stylus files to start looking for an issue.

#Getting all media queries present in a stylesheet

Note: Access to the cssRules attribute is subject to the cross-origin policy
Some thoughts on getting around that issue here

var mediaQueriesMap = new Map();

var rules;


//--isolate the style sheet you want to work with. 

//set it directly by picking it from the styleSheets array. Here we're using the sheet at index 3.
rules = document.styleSheets[3].cssRules;

//set it by referencing it's url
function getStyleSheetByUrl(url){
    for (var i = document.styleSheets.length - 1; i >= 0; i--) {
        if( document.styleSheets[i].href == url){
            rules = document.styleSheets[i].cssRules;
        }
    }
}
getStyleSheetByUrl('http://www.webste.com/style.css');


//--create the map 
Array.prototype.forEach.call(rules, function(rule,i){
    if(rule instanceof CSSMediaRule){
        var mediaQueryRule = rule.media.mediaText;
        if(!mediaQueriesMap.has(mediaQueryRule)){//adds rule to the map if missing
            mediaQueriesMap.set(mediaQueryRule, {styleRules:[], occurrences:0});
        }
        //log the rule's occurance
        mediaQueriesMap.get(mediaQueryRule).occurrences++;
        //add this media queries style rules to the map
        Array.prototype.forEach.call(rule.cssRules, function(styleRule,i){        
            mediaQueriesMap.get(mediaQueryRule).styleRules.push(styleRule);        
        });
    }
});

//--print results to a console table 
var reportingTable = []
for (var entry of mediaQueriesMap){
    reportingTable.push( {
        mediaQuery:entry[0], 
        occurrencesInStylesheet:entry[1].occurrences,
        countOfStyleRules:entry[1].styleRules.length
    });
}

console.table(reportingTable);


//optional - print results to a csv formated string that can be saved via a text editor
/*
var csv = '--Media Query--,--times it appears--,--style rules within this media rule--\n';
for (var entry of mediaQueriesMap){ 
    csv+=entry[0]+', '+entry[1].occurrences+', '+entry[1].styleRules.length+'\n'
}
console.log(csv);
*/

##Auditing media queries using JavaScript

TL;DR: You can use JavaScript to easily audit the media queries that are present in your project. This could potentially provide insight into portions of your styling that might need to be examined more closely. Don't care about the rational behind doing something like this and are just looking for a snippet? Head here.

While the overall thoughtfulness around how frontends are built has improved in recent years, generally speaking, styling simply isn't treated with the same amount of care and consideration as other parts of the front-end stack. Marry this apathy with tools like Sass/LESS/Stylus which enable developers to easily write complex and powerful CSS, and it's easy to envision the sort of tangled, nightmarish abomination that might exist in this world.

In my experience, this usually takes the form of a framework like bootstrap or foundation that has had so many layers of paint and ad hoc fixes applied to it that Theseus would be proud.

The likelihood of confronting this sort of monstrosity is never higher then when a developer inherits an existing web project.

On the surface, poorly written styling might not seem like a big issue.

Unfortunately, when the time comes to make updates to the project, the flaws and idiosyncrasies that exist in the CSS can quickly become a massive time sink as ad hoc fixes for one problem cascades into other issues. The ideal course of action would be to go through and completely refactor the styling, but due to time constraints and the perceived magnitude of the task, it's something that is rarely is attempted. If only there was a way to easily see where potential issues might exist in order to provide a starting place for the process...

In auditing the our media queries, we can

An audit of the a sites media queries can:

  • highlight places where mixed length unit values might have been used
  • spot inconsistent or mistyped values
  • find potential style overlaps and places to check for unintentional "double styling"
  • point to weird-edge cases & one-offs that could potentially be investigated.

...and best of all this discovery and documentation process can be automated using JavaScript.

####What are we doing

So what exactly will be happening?

  • Pick a target style sheet and get it's cssRuleList
    Note: Keep in mind that style sheets are subject to the same-origin policy. This means that if the style sheet's origin doesn't match that of the page (e.g. being loaded from a CDN), the cssRules property of the CSSStyleSheet object isn't available and returns null. Some thoughts on dealing with this issue can be found here.
  • loop through this collection of CSSRules looking for CSSMediaRules
  • When one is found,
    • if it hasn't been seen before, map it to mediaQueriesMap variable we have setup,
    • increment the count value for this CSSMediaRule in the map,
    • add all cssRules found within this particular instance of the media query to the entry in the mediaQueriesMap variable,
  • Finally we format mediaQueriesMap into a table and print it in the console.

code

console.log('There will be javascript here');



  • bring code into a unified and consistent state
  • preventing the mixing of absolute & font-relative length units.
  • easily spot inconsistent or mistyped values
  • help to spot overlaps and places to check for unintentional "double styling"
  • highlight weird-edge cases & one-offs that could potentially be investigated.

This means that,

Unit testing CSS isn't Wading into a

Unit testing CSS isn't really a thing.

If you don't care about the rational behind doing something like this and are just looking for a snippet, go here

####problem/why

  • bring code into a unified and consistent state
  • preventing the mixing of absolute & font-relative length units.
  • easily spot inconsistent or mistyped values
  • help to spot overlaps and places to check for unintentional "double styling"
  • highlight weird-edge cases & one-offs that could potentially be investigated.

mixing absolute & font relative lengths are by far the most widely used length values that are used in media queries mixing them can have

  • easily spot inconsistent or mistyped values (#17 vs #16,#19,21 )
  • help to spot overlaps and unintentional "double styling" and element that is being styled at 768 (#21(min-768) & #16,19(max-768))
  • highlight one-offs, weird edge-cases,
  • with k

other solutions

While CSS unit and regression testing do exist, they are far from the standard industry practice.

This means we are basically left with two options when dealing with styling:

  • Manually going through all of the includes, noting any media queries that are uncovered,
  • or wait until you see unexpected behavior and debug issues on an ad-hoc basis

One is incredibly time consuming & tedious. The other is completely reactive & subject to a large degree of chance.

99% of the time, developers opt for the second option. (if it ain't broke...)

We can automate the discovery and documentation process using javascript.

####running the code

So what exactly will be happening?

  • Pick a target style sheet and get it's cssRuleList Note: Keep in mind that style sheets are subject to the same-origin policy. This means that if the style sheet's origin doesn't match that of the page (e.g. being loaded from a CDN), the cssRules property of the CSSStyleSheet object isn't available and returns null. Some thoughts on dealing with this issue can be found here.
  • loop through this collection of CSSRules looking for CSSMediaRules
  • When one is found,
    • if it hasn't been seen before, map it to mediaQueriesMap variable we have setup,
    • increment the count value for this CSSMediaRule in the map,
    • add all cssRules found within this particular instance of the media query to the entry in the mediaQueriesMap variable,
  • Finally we format mediaQueriesMap into a table and print it in the console.

####code

console.log('There will be javascript here');



####analyzing the results

So what are we looking for?

In the example below, I've run the audit script on a project that I recently inherited.

At the top of table, we see several media queries that contain the majority of the rules for this particular style sheet.

We already knew that this project was built using Foundation 5, so seeing a majority of the media features statements

At the top of table we see several media queries that contain the majority of the rules for this project. We know that this project was built using Foundation 5, so seeing their media feature definitions using the em length units is to be expected. These media queries are most likely using Foundation's built in variables or at the very least are defined using a value that is consistent with those variables.

Around #14 ("screen and (max-width: 1010px)) we see the media queries begin to be defined using the absolute length unit of px.
While the arguments can be made for using either length unit (px or em) in media query definitions, the bigger issue in this particular instance is of consistency. With the vast majority of the styling rules being housed in em based media queries, these px based definitions stand out, and should be examined more closely for code smell, adherence/disregard for best practices, and broken code. At the very least, they should be rewritten to use the established methodology of the rest of the project.

The next point of interest involves #17 ( "screen and (max-width: 770px)" ). Beyond the previously mentioned length unit inconsistencies, their doesn't seem to be anything inherently wrong with this particular media query at first glance. The issue starts to become more apparent when it is compared against some of the other media queries. #16 ( "screen and (max-width: 768px) and (min-width: 320px)" ) and #19 ( "screen and (max-width: 768px)" ) are using incredibly similar (but not quite matching) values in their definitions. The odds of a specific rule needing to be exactly 2px smaller than an established breakpoint isn't very high. The more likely explanation is that the author either forgot or wasn't aware of the exact breakpoint that is used elsewhere in the project. Either way, this can be viewed as something that should be flagged for a closer look.

Finally, #20 ( "screen and (min-width: 768px)" ) is classic example of an overlap taking place.

Finally, #20 ( "screen and (min-width: 768px)" ) appears to be overlapping with some media queries that are set to use "max-width: 768px" ( #16 & #19).

We know that with some media queries that are set to use "max-width: 768px" ( #16 & #19)

It's no accident that frameworks like Bootstrap & Foundation define their breakpoints in a consistent manor that prevents overlap (examples A & B). It's no accident that CSS frameworks define their breakpoints in a consistent manor that prevents overlap (e.g. Bootstrap & Foundation).

So, when there are multiple definitions checking a particular media feature for an identical value but with opposing prefixes, it is something that should be investigated.

Perhaps it's just a simple inconsistency in how a particular breakpoint was authored, but there is also a possibility of a huge issue where multiple elements are getting conflicting styling instructions when the width is 768px.

but it has the potential to but there is also a possibility of a huge issue where multiple elements are getting conflicting styling instructions when the width is 768px.

a large set of elements are getting conflicting styling instructions when the width is 768px.

At best, it's just a simple inconsistency in how a that particular breakpoint was authored,

in a way that prevents them from over having multiple definitions checking a media feature for and identical value, but with opposing prefixes,

at worst a set of elements are getting conflicting styling instructions when the width is 768px.

The reason that frameworks like Foundation & Bootstrap define their breakpoints so that they don't

Foundation Bootstrap

The general idea is to try to survey the CSS landscape from 30,000 feet,

and look for things that seem different or stand out. Developers are creatures of habbit. If you see smoke rising out of the forest, it's probably worth taking a closer look

forest from

forCSS from 30,000 feet. If you see smoke What are doing once we have this data?

Once we have this data,

What do we do with this data? The general idea is to look for things that that seem different or stand out. Developers are creatures of habit, so if something seems inconsistent, peculiar, or out-of-place, it's likely that it was written by a different developer, or under different circumstances. These are the places we want to initially focus on, as they are more likely yield potential refactoring opportunities. At the very least they can be brought into alignment with the rest of the codebase to help make everything more maintainable going forward.

Specifically, we're on the looking for things like:

on as they'll pro attention What are doing once we have this data?

The general idea is to try to survey the CSS landscape from 30,000 feet.

It's unlikely (but not impossible) that anything will appear to be outright broken from this vantage point i,

We obviously want to notice any glaring issues, but if this is a codebase that is already in production, the likelihood of finding stuff that is broken at this

things that seem different or stand out.

Once we have this data, what are we looking for?

Generally speaking we're on the look out for:

The author either forgot, or wasn't aware of the exact breakpoint that is used elsewhere in the project. Either way, this can be viewed as something that should be flagged for a closer look.

forgetting the exact breakpoint that they should be using

There a couple which are using incredibly close (but not quite matching) values in their definitions, that are using similar values in their definitions. There are a few that are it's apparent that

Taking a look at some of the other media queries which Looking at media queries that have similar

multiple other media queries are using an incredibly close (but not quite matching) values in their definitions. = #16 ( "screen and (max-width: 768px) and (min-width: 320px)" ) = #19 ( "screen and (max-width: 768px)" )

= #21 ( "screen and (min-width: 768px)" )

This could be an instance of or the author forgetting the exact breakpoint easily spot inconsistent or mistyped values

(#17 vs #16,#19,21 )

Within the collection of px defined

it provides a starting point from which to being

...at the very least, these should be rewritten to used the established methodology.

refactor

At the top of table, we see -several media queries- that contain the majority of the rules for this particular style sheet. They're all ...using ...looking for length type media features using the em

We already knew that this project was built using foundation, so seeing a majority of the rules inside media queries that are using em length units is to be expected. These media queries are most likely using Foundations built in variables, or at the very least are using a consistent unit of measurement.

  • easily spot inconsistent or mistyped values (#17 vs #16,#19,21 )
  • help to spot overlaps and unintentional "double styling" and element that is being styled at 768 (#21(min-768) & #16,19(max-768))
  • highlight one-offs, weird edge-cases,
  • with k



expanding the usage

The mediaQueriesMap map that is created offers a fair bit of utility offers a fair bit of utility The mediaQueriesMap map that is created offers a fair bit of utility that can be expanded on and used The mediaQueriesMap map that is created offers a fair bit of flexibility ...that can be utilized and expanded on. The mediaQueriesMap map that is created is fairly flexible and can be used to produce additional insights.

The mediaQueriesMap map that is created is fairly flexible. In addition to creating a general overview, it can be used to drill into specific media queries. From the previous In the prior example we can see that the media query screen and (max-width: 1000px) is being used in the style sheet 13 different times to declare 13 different rules. Using our existing mediaQueriesMap variable we can see what selectors are being used for each of those rules.

var rules_at_this_breakpoint = mediaQueriesMap.get("screen and (max-width: 1000px)").styleRules


Array.prototype.forEach.call(rules_at_this_breakpoint, function(rule){
    console.log(rule.selectorText);
});

The resulting output could be useful in tracking down where exactly within the a project's uncompiled Sass/LESS/Stylus files to start looking for an issue.

a note about working with styles sheets,

There are two methods specified

Note: Keep in mind that style sheets are subject to the same-origin policy. This means that if the style sheet's origin doesn't match that of the page (e.g. being loaded from a CDN), the cssRules property of the CSSStyleSheet object isn't available and returns null. Some thoughts on dealing with this issue can be found here.

##Auditing media queries using javascript

This isn't a bad idea to Just want the code? Here it is.

While CSS unit testing does exist, it's far

Unit testing CSS isn't Wading into a

Unit testing CSS isn't really a thing. Don't care about the reasoning for doing something like this, or what sort of insight

Just looking for the snippet If you don't care about the rational behind doing something like this

####problem/why

  • bring code into a unified and consistent state
  • -easily spot inconsistent or mistyped values
  • -help to spot overlaps and places to check for unintentional "double styling"
  • -highlight weird-edge cases & one-offs that could potentially be investigated.

mixing
absolute & font relative lengths are by far the most widely used length values that are used in media queries mixing them can have

  • easily spot inconsistent or mistyped values (#17 vs #16,#19,21 )
  • help to spot overlaps and unintentional "double styling" and element that is being styled at 768 (#21(min-768) & #16,19(max-768))
  • highlight one-offs, weird edge-cases,
  • with k

####other solutions manually going through all of the includes, noting any media query that or wait until you see unexpected behavior and debug the issue then.

One is incredibly time consuming & tedious. The other is completely reactive & subject to a large degree of chance.
There are a couple of ways to deal with....
The first is to manually go through all of module, partial, import, include, vendor, manifest, primary, etc. files, noting any media query that is uncovered.
Unfortunately, any semi-robust or componentized project that is using a preprocessor will probably have a large number of files which would make this process incredibly time consuming and tedious.

A robust and heavily componentized project that is using a preprocessor could have a large number of include files which would make option A incredibly time consuming and tedious.

####code --targeting a stylesheet

####running the code

item in that collection,
map the mediaQueryRules

####analyzing the results

At the top of table, we see several media queries that contain the majority of the rules for this particular stylesheet.
They're all
...using
...looking for length type media features using the em

We already knew that this project was built using foundation, so seeing
...this
...a majority of the rules inside media queries that are using em length units is to be expected.
These media queries are most likely using Foundations built in variables, or at the very least are using a consistent unit of measurement.

and checking the documentation They are all using em as their unit of measurement.

em that contain a large
large collections chunk of style rules that were created inside media

all of media queries that were created using foundation variables

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment