Ever been the new person on an old project where you were trying to not make things substantially worse? Maybe you need to position an element, but you don’t know what z-index
value is safe to use. You don’t want to layer something to z-index: 9999
if z-index: 10
will suffice… but how do you find out what the highest z-index
is?
Or, for that matter, how do you find everything that’s using opacity
?
Sure, you’ve got a modern browser with great debugging tools. But they don’t make it easy for you to search by CSS properties. So I wrote a JavaScript function that can do that.
Remember the CSSOM?
I’ve written about the CSSOM in the past (CSS Object Model) and I’ll say again that it’s an under-appreciated and under-utilized feature of the web browser environment.
If you need to do something so peculiar as searching for selectors by CSS properties, this is the thing the CSSOM is good for.
It starts with the stylesheets
Let’s start by creating a new function that’ll take two arguments; queryPropName
and queryPropValue
. We’ll access some styleSheets
and loop through them since they’re enumerable, but not an array.
function queryCSSByProperty(queryPropName, queryPropValue) {
const styleSheets= document.styleSheets;
const properties = new Map();
Object.values(styleSheets).forEach(styleSheet => {
});
}
Next, try to loop through the rules
We’ll need to wrap the interior of our loop in a try-catch because the browser may throw some restrictions at us trying to access some of the cssRules
. Then we’ll loop through the rules of that styleSheet
Object.values(styleSheets).forEach(styleSheet => {
try {
const rules = styleSheet.cssRules;
Object.values(rules).forEach(rule => {
});
} catch (err) {
console.log(err);
}
Then, look for a style on the rule
Now that we’re inside of a rule inside of a stylesheet
, we’re where we need to be. So now what we want to do is see if the CSS property we want is there.
First, let’s pull out some things that will be useful, like style
, which will be an object that contains every possible CSS property. Let’s also grab some selectorText
, which is strictly the CSS selector part of this ruleset (e.g. .someSelector > h1 > .foo
)
Once we get that out, let’s be safe and check to be sure style is truthy. Not every cssRule
will have a style property.
Object.values(rules).forEach(rule => {
const {style, selectorText} = rule;
if (style) {
}
});
And if there’s a style, check if it’s the one you want
This is where some logic could probably be cleaned up.
First, let’s use the CSSOM
, rather than hasOwnProperty
or for–in to find out if there’s a value for our property. getPropertyValue
turns out to be the perfect way to get the value off of a specific CSS property in our style.
Next, because we want an option to see search by value of a property, we have hasPropValMatch
to tell us exactly that. And for right now, rather than ===
, let’s use indexOf
because there’s always the chance that there’s an !important
floating here. It’d be better to search within the string rather than assume it’s a pure equality. We’ll probably need more robust matching in the future.
Finally, we check if our queryPropValue
is there (because, after all, maybe you’re just interested in z-index
, not z-index: 10
). If it is, let’s set a value to our map.
We’ll make the key of our item the CSS selector, which is the selectorText
. Then we’ll make the value a mashup of the property name and the property value. It won’t be a duplicate of what’s written in the actual stylesheet — just what’s important for search purposes.
if (style) {
const propertyValue = style.getPropertyValue(queryPropName);
const hasPropValMatch = queryPropValue && propertyValue.indexOf(queryPropValue) !== -1;
if (queryPropValue && hasPropValMatch) {
properties.set(selectorText, `${queryPropName}:${propertyValue}`);
}
if (!queryPropValue && propertyValue) properties.set(selectorText, `${queryPropName}:${propertyValue}`);
}
Finally, stitch it all together
Once we’ve done all this, let’s make sure that we return those properties we’re setting values in:
function queryCSSByProperty(queryPropName, queryPropValue) {
const styleSheets= document.styleSheets;
const properties = new Map();
if (!queryPropName) return properties;
Object.values(styleSheets).forEach(styleSheet => {
try {
const rules = styleSheet.cssRules;
Object.values(rules).forEach(rule => {
const {style, selectorText} = rule;
if (style) {
const propertyValue = style.getPropertyValue(queryPropName);
const hasPropValMatch = queryPropValue && propertyValue.indexOf(queryPropValue) !== -1;
if (queryPropValue && hasPropValMatch) {
properties.set(selectorText, `${queryPropName}:${propertyValue}`);
}
if (!queryPropValue && propertyValue) properties.set(selectorText, `${queryPropName}:${propertyValue}`);
}
});
} catch (err) {
console.log(err);
}
});
return properties;
}
Final Result?
You should be able to run queryCSSByProperty('z-index')
on this page and discover that there's over 60 CSS rulesets using z-index. Quite nicely though for dev.to, they're managing most of them with CSS custom properties.
A function like this will help you not be that person who’s picking random numbers out of a hat. In fact, a function like this might be a useful way to make sure your element is always lastzIndex + 1
.
Of course, this could have other uses, too, beyond z-index
. But you get the idea.
BTW, the final version of this is a gist, if that’s what you’re looking for.
Top comments (0)