I have a some Javascript which creates and maintains a string of CSS, for example:
".joe{top:1rem} @media(min-width: 64rem){ .blogs{top:3rem} }"
Part of the logic can add a new CSS rule inside a @media query. Currently, it adds the rule after the opening { bracket of the @media query, because it's quite easy to search for @media(min-width: 64rem){ and then insert at that index, for example:
".joe{top:1rem} @media(min-width: 64rem){ .new{top:3rem} .blogs{top:3rem} }"
Here's a snippet of the logic:
// find the existing media query from the style tag
// styleTagCache is the CSS string being maintained
const searchString = `@media(min-width:${breakpoint}){`;
const mqIndex = styleTagCache.indexOf(searchString) + searchString.length;
// insert rule into the media query
const styleTagArray = [...styleTagCache];
styleTagArray.splice(mqIndex, 0, ruleString);
styleTagCache = styleTagArray.join('');
However, I'd like to insert the new rule at the end of the media query, before its closing } bracket, like this:
".joe{top:1rem} @media(min-width: 64rem){ .blogs{top:3rem} .new{top:3rem} }"
I've had a few futile attempts, but can't figure it out. The difficult part seems to be searching for the media query's closing }, but ignoring any other } which are part of the CSS rules inside it.
You can iterate over the string after the occurrence of the media query to find the closing curly bracket and then insert the new rule:
const css = ".joe{top:1rem} @media(min-width: 64rem){ .blogs{top:3rem} }";
console.log(add(css, '64rem', '.new{top:3rem}'))
function add(css, breakpoint, rule) {
const niddle = `@media(min-width: ${breakpoint}){`;
let index = css.indexOf(niddle);
if (index === -1)
throw new Error('Media query not found');
index += niddle.length;
let balance = 0, pos = null;
for (let i=index; i<css.length; i++) {
const char = css[i];
if (char === '{')
balance++;
else if (char === '}')
balance--;
if (balance === -1) {
pos = i-1;
break;
}
}
if (pos === null)
throw new Error('Invalid css');
return css.substring(0, pos) + ` ${rule} ` + css.substring(pos);
}
I don't know if this is interesting to you, but you can actually traverse all the CSS rules in JavaScript. So, here I find and print out the background color for a condition and add a new property (the border).
Depending on the complexity of you code it is not an easy task to find the right CSS rule and modify it, but it can be done...
var cssRules = document.styleSheets[0].cssRules;
// printout the background-color:
console.log([...cssRules].filter(r => r.conditionText == '(min-width: 10rem)')[0].cssRules[0].style.backgroundColor);
// add or modify a rule:
[...cssRules].filter(r => r.conditionText == '(min-width: 10rem)')[0].cssRules[0].style.border = 'black solid 5px';
@media(min-width: 10rem) {
.new {
background-color: red;
}
}
<div class="new">new</div>
I think that it would make sense to maintain an object that holds all the properties of that @media rule. Then you can add more selectors on the fly and render the string again using the function.
The object I created is simple. You can make a more advanced object if needed.
const renderrule = rule => {
let selectorsstr = rule.selectors.map(s => `${s.selector}{${s.props}}`).join(' ');
return `@media{(${rule.condition}) ${selectorsstr}}`;
};
var mediarule = {
condition: 'min-width: 64rem',
selectors: [{
selector: '.blogs',
props: 'top:3rem'
},
{
selector: '.new',
props: 'top:3rem'
}
]
};
console.log(renderrule(mediarule));
// adding to the rule:
mediarule.selectors.push({selector: '.new2', props: 'top:2rem'});
console.log(renderrule(mediarule));