Skip to content

Instantly share code, notes, and snippets.

@ragulka
Created September 2, 2013 13:36
Show Gist options
  • Save ragulka/6412932 to your computer and use it in GitHub Desktop.
Save ragulka/6412932 to your computer and use it in GitHub Desktop.
Use CSS Specifity calculator (https://github.com/keeganstreet/specificity) to get the applied CSS rule for an element. Based on http://jsfiddle.net/markcoleman/TKHpV/5/ and http://jsfiddle.net/5Cyy3/ Basically, this is window.getMatchedCSSStyles for all browsers, plus the extra benefit of actually getting the applied rule. For example, you can d…
/**
* Calculates the specificity of CSS selectors
* http://www.w3.org/TR/css3-selectors/#specificity
*
* Returns an array of objects with the following properties:
* - selector: the input
* - specificity: e.g. 0,1,0,0
* - parts: array with details about each part of the selector that counts towards the specificity
*/
var SPECIFICITY = (function() {
var calculate,
calculateSingle;
calculate = function(input) {
var selectors,
selector,
i,
len,
results = [];
// Separate input by commas
selectors = input.split(',');
for (i = 0, len = selectors.length; i < len; i += 1) {
selector = selectors[i];
if (selector.length > 0) {
results.push(calculateSingle(selector));
}
}
return results;
};
// Calculate the specificity for a selector by dividing it into simple selectors and counting them
calculateSingle = function(input) {
var selector = input,
findMatch,
typeCount = {
'a': 0,
'b': 0,
'c': 0
},
parts = [],
// The following regular expressions assume that selectors matching the preceding regular expressions have been removed
attributeRegex = /(\[[^\]]+\])/g,
idRegex = /(#[^\s\+>~\.\[:]+)/g,
classRegex = /(\.[^\s\+>~\.\[:]+)/g,
pseudoElementRegex = /(::[^\s\+>~\.\[:]+|:first-line|:first-letter|:before|:after)/gi,
// A regex for pseudo classes with brackets - :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-type(), :lang()
pseudoClassWithBracketsRegex = /(:[\w-]+\([^\)]*\))/gi,
// A regex for other pseudo classes, which don't have brackets
pseudoClassRegex = /(:[^\s\+>~\.\[:]+)/g,
elementRegex = /([^\s\+>~\.\[:]+)/g;
// Find matches for a regular expression in a string and push their details to parts
// Type is "a" for IDs, "b" for classes, attributes and pseudo-classes and "c" for elements and pseudo-elements
findMatch = function(regex, type) {
var matches, i, len, match, index, length;
if (regex.test(selector)) {
matches = selector.match(regex);
for (i = 0, len = matches.length; i < len; i += 1) {
typeCount[type] += 1;
match = matches[i];
index = selector.indexOf(match);
length = match.length;
parts.push({
selector: match,
type: type,
index: index,
length: length
});
// Replace this simple selector with whitespace so it won't be counted in further simple selectors
selector = selector.replace(match, Array(length + 1).join(' '));
}
}
};
// Remove the negation psuedo-class (:not) but leave its argument because specificity is calculated on its argument
(function() {
var regex = /:not\(([^\)]*)\)/g;
if (regex.test(selector)) {
selector = selector.replace(regex, ' $1 ');
}
}());
// Remove anything after a left brace in case a user has pasted in a rule, not just a selector
(function() {
var regex = /{[^]*/gm,
matches, i, len, match;
if (regex.test(selector)) {
matches = selector.match(regex);
for (i = 0, len = matches.length; i < len; i += 1) {
match = matches[i];
selector = selector.replace(match, Array(match.length + 1).join(' '));
}
}
}());
// Add attribute selectors to parts collection (type b)
findMatch(attributeRegex, 'b');
// Add ID selectors to parts collection (type a)
findMatch(idRegex, 'a');
// Add class selectors to parts collection (type b)
findMatch(classRegex, 'b');
// Add pseudo-element selectors to parts collection (type c)
findMatch(pseudoElementRegex, 'c');
// Add pseudo-class selectors to parts collection (type b)
findMatch(pseudoClassWithBracketsRegex, 'b');
findMatch(pseudoClassRegex, 'b');
// Remove universal selector and separator characters
selector = selector.replace(/[\*\s\+>~]/g, ' ');
// Remove any stray dots or hashes which aren't attached to words
// These may be present if the user is live-editing this selector
selector = selector.replace(/[#\.]/g, ' ');
// The only things left should be element selectors (type c)
findMatch(elementRegex, 'c');
// Order the parts in the order they appear in the original selector
// This is neater for external apps to deal with
parts.sort(function(a, b) {
return a.index - b.index;
});
return {
selector: input,
specificity: '0,' + typeCount.a.toString() + ',' + typeCount.b.toString() + ',' + typeCount.c.toString(),
parts: parts
};
};
return {
calculate: calculate
};
}());
// Export for Node JS
if (typeof exports !== 'undefined') {
exports.calculate = SPECIFICITY.calculate;
}
/**
* Get matches CSS rules of element
*/
function getMatchedStyle (elem, property) {
// Element property has highest priority
var val = elem.style.getPropertyValue(property);
// If it's important, we are done
if (elem.style.getPropertyPriority(property)) return val;
// Get matched rules
var rules = getAppliedCssRules(elem);
// Iterate the rules backwards
// Rules are ordered by priority, highest last
for (var i = rules.length; i --> 0;) {
var r = rules[i];
var important = r.style.getPropertyPriority(property);
// If set, only reset if important
if (!val || important) {
val = r.style.getPropertyValue(property);
// Done if important
if (important) break;
}
}
return val;
}
function getAppliedCssRules(element) {
var appliedRules = [],
powers = [1000,100,10,1];
for (var x = 0; x < document.styleSheets.length; x++) {
var rules = document.styleSheets[x].cssRules;
for (var i = 0; i < rules.length; i++) {
if ($(element).is(rules[i].selectorText)) {
appliedRules.push(rules[i]);
}
}
}
function comparator (rule1, rule2) {
var s1 = SPECIFICITY.calculate(rule1.selectorText),
s2 = SPECIFICITY.calculate(rule2.selectorText),
total1 = 0,
total2 = 0;
$.each(s1[0].specificity.split(','), function(i, value) {
total1 += value * powers[i];
});
$.each(s2[0].specificity.split(','), function(i, value) {
total2 += value * powers[i];
});
return total1 - total2;
}
appliedRules.sort(comparator)
return appliedRules;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment