-2

Lots of answers on how to get/set "root" CSS variables but none that I've found on how to get/set NON root CSS variables.

NOTE: This is NOT answer: https://stackoverflow.com/a/36088890/104380 That answer only handles root variables and or variables on elements, not on classes

Also: These are not answers (from: https://www.google.com/search?q=get%2Fset+css+variables). Checked the first page of links. All of them only handle root variables. I'm trying to deal with non-root variables.

.foo {
  --fg-color: red;
}
.bar {
  --fg-color: blue;
}
div {
 color: var(--fg-color);
}
<div class="foo">foo</div>
<div class="bar">foo</div>

How do you get someFunctionToGetCSSVariable('.foo', '--fg-color') (yes, I made that up). The point is I want to get/set the --fg-color variable on the .foo CSS rule. Whatever the solution is, getting the value it should return 'red' or '#FF0000') and how do you set it to something else?

gman
  • 100,619
  • 31
  • 269
  • 393
  • I think you may find your answers here https://stackoverflow.com/questions/43028606/compile-non-root-css-custom-property – Nick Vu Jul 03 '22 at 06:41
  • 1
    What do you want to achieve? CSS variables work like CSS properties, so you can set them on individual elements, for one. The direct answer to your question is dynamically modifying or creating CSS rules, but that’s not clean at all and indicates some kind of misapplication of concepts. If they represent some kind of shared state, it’s probably more appropriate to set them on a common ancestor element (including `body` or `html`/`:root`) and let them be inherited by the relevant elements, maybe with some indirection (`--fg-color: var(--foo-fg-color)`). – Ry- Jul 03 '22 at 06:43
  • possible duplicate of: https://stackoverflow.com/q/6620393/8620333 – Temani Afif Jul 03 '22 at 08:11
  • Sorry about assuming earlier. I now understand you only want to parse CSS rules without HTML. I would have been helpful if you wrote live code in your question and explicitly mention that). You would need to iterate over all the applied styles and search for a specific selector and parse its content to extract the variable's value. Your question becomes 3 questions: **1** - How to iterate all styles **2** - how to to find a specific selector's properties **3** - how to parse a selector's property to extract its value – vsync Jul 03 '22 at 08:35

7 Answers7

0

I think what OP tries to do is not to change the all elements of that certain class, but to change the class itself, there is a difference. What you need (if I understood well) is actually cssRules change on the certain class. I've created a quick (and dirty) snippet of how to search the cssRules and change the CSS properties in it (including custom one):

<html>
<head>
<style>
.foo {
  --fg-color: red;
}
</style>
</head>
<body>
<div class="foo">lorem ipsum</div>



<script>
window.onload = function() {
  rules = document.styleSheets[0].cssRules
  for (var r in rules) {
      if (rules[r].selectorText == ".foo") {
        rules[r].style.setProperty('--fg-color', 'blue');
        alert('hi');
        break;
      }
  }
};
</script>
</body>
</html>
user5214530
  • 467
  • 5
  • 11
0

You could make use of the fact that we have cascading style sheets.

It depends on exactly what your structure is of course, (e.g. are there style elements buried in body?).

For the simple case you could add another stylesheet onto the bottom of the head element.

<!doctype html>
<html>
<head>
<style>
.foo {
  --fg-color: red;
}
.bar {
  --fg-color: blue;
}
div {
 color: var(--fg-color);
}
</style>
</head>
<body>
<div class="foo">foo</div>
<div class="bar">foo</div>
<button>Click me</button>
<script>
const button = document.querySelector('button');
button.addEventListener('click', function () {
  const head = document.querySelector('head');
  const style = document.createElement('style');
  style.innerHTML = '.foo { --fg-color: lime; }';
  head.appendChild(style);
  button.style.display = 'none'; 
});
</script>

</body>
</html>

Of course, it could get messy if this happens more than once. You'd want to remember that you'd added a style sheet and get rid of it before adding another one, or alter it in situ, depending on exactly what the situation is. And if there is styling scattered in the body you'll have to add the stylesheet to the bottom of that.

A Haworth
  • 30,908
  • 4
  • 11
  • 14
0

Looks like I have to iterate through all the stylesheets and find the rule. Here's one try.

function getCSSRuleBySelector(selector) {
  for (let s = 0; s < document.styleSheets.length; ++s) {
    const rules = document.styleSheets[s].cssRules;
    for (let i = 0; i < rules.length; ++i) {
      const r = rules[i];
      if (r.selectorText === selector) {
        return r;
      }
    }
  }
}

function setCSSVariableBySelector(selector, varName, value) {
  const rule = getCSSRuleBySelector(selector);
  rule.style.setProperty(varName, value);
}

function getCSSVariableBySelector(selector, varName) {
  const rule = getCSSRuleBySelector(selector);
  return rule.style.getPropertyValue(varName);
}

console.log('was:', getCSSVariableBySelector('.foo', '--fg-color'));
setCSSVariableBySelector('.foo', '--fg-color', 'green');
.foo {
  --fg-color: red;
}
.bar {
  --fg-color: blue;
}
div {
  color: var(--fg-color);
}
<div class="foo">foo</div>
<div class="foo">foo</div>
<div class="foo">foo</div>
<div class="bar">bar</div>
<div class="bar">bar</div>
gman
  • 100,619
  • 31
  • 269
  • 393
  • Yep, exactly like that! The difference between this method and querySelector presented in other solutions is that if you dynamically add the new element of the same class, modified rules from the cssRules will be applicable to newly generated elements as well, while with the querySelector, you would have to re-run it to pick up new elements. – user5214530 Jul 03 '22 at 10:40
0

You can use document's styleSheets property to access the CSS rules of the linked .css file, and perform some operations on it to get the value of the custom css variable.
To set a value of custom CSS variable (of the class itself), you can create a new style element, and add a rule for it.

function getStyle(selector, prop) {
  let rules = getRule(selector).cssText;
  return rules.split(prop + ":")[1].replace(";", "").replace("}", "").trim();
}

function setStyle(selector, prop, val) {
  let style = document.querySelector("style.customStyle");
  if(style === null){
    style = document.createElement("style");
    style.className = "customStyle";
    style.innerHTML = `${selector}{${prop}:${val}}`;
    document.head.appendChild(style);
  } else{
    style.innerHTML += `${selector}{${prop}:${val}}`;
  }
}

function getRule(selector) {
  let rulesObj = document.styleSheets[0];
  let classes =  rulesObj.rules || rulesObj.cssRules;
  classes = Object.values(classes)
  let rules = classes.filter(c => c.selectorText === selector)[0];
  return rules;
}

console.log(getStyle(".foo", "--fg-color"));
console.log(getStyle(".bar", "--fg-color"));

document.querySelector("button").onclick = ()=>{
  setStyle(".bar", "--fg-color", "green");
}
.foo {
  --fg-color: red;
}

.bar {
  --fg-color: blue;
}

div {
  color: var(--fg-color);
}
<div class="foo">foo</div>
<div class="bar">foo</div>
<div><button>Click</button></div>
TechySharnav
  • 4,869
  • 2
  • 11
  • 29
0

Get

Iterate all styles

Styles can be injected via external CSS file, a <style> tag (on the page) or style attribute (irrelevant to you).

const results = [];
const SELECTOR = '.foo';
const PROP = '--bar';

[...document.styleSheets].forEach(sheet => {
  [...sheet?.cssRules].forEach(rule => {
    // you should probably use regex to find PROP (so it won't match `--bar-x` for example)
    if( rule.cssText.includes(SELECTOR) && rule.cssText.includes(PROP) ){
      results.push(rule.cssText)
    }
  })
});

console.log(results)
.foo { --bar: red }

Now you need to parse the result and extract the property value. You did not specify in your question if CSS selectors' specificity should be regarded, which makes things a lot more complex.

There might also be selectors which implicitly applies style properties to elements. Examples of different specificities:

/* assuming '.foo' is a div child of <body> */
.foo { --x:1 }  
body .foo { --x:2 }  
body > div { --x:3 }
body * { --x:4 }
div { --x:5 }

Set

Regarding setting, you need to dynamically add a rule or a style tag:

document.head.insertAdjacentHTML("beforeend", `<style id="whatever">--fg-color: red;</style>`)

And when you want to update it, just overwrite the #whatever <style> with innerHTML.

vsync
  • 118,978
  • 58
  • 307
  • 400
-2

To set the value you can use document.querySelectorAll and the setProperty method on the style of each object:

function setCssVariable(selector, variable, value) {
    document.querySelectorAll(selector).forEach(function (element) {
        element.style.setProperty(variable, value);
    });
}

setCssVariable(".foo", "--fg-color", "black");

And to get the value, assuming all elements of the class have the same value, you can use a similar approach using document.querySelector and the getProperty method on style of the element:

function getCssVariable(selector, variable) {
    const element = document.querySelector(selector);
    if (element !== null) {
        return element.style.getPropertyValue(variable);
    }
}

getCssVariable(".foo", "--fg-color");
Beni Trainor
  • 346
  • 1
  • 11
-2

You can read the variable value from the getComputedStyle method of JS and set the variable value with style attribute.

let foo = document.querySelector('.foo');

/*You can get the value of variable property from the element where you used the variable*/
//console.log(getComputedStyle(foo).getPropertyValue('--fg-color'));

/*You can set the variable value like this for every element where used the variable and want to choose at runtime with js */
//foo.style.cssText = '--fg-color:black;';



function setCSSProperty(cls, prop, val){
  
  let els = document.querySelectorAll(cls);
  if(els){
    els.forEach(el => {
      el.style.cssText += `${prop}:${val};`
    })
  }

}

function getCSSPropertyData(cls, prop){
  
  let els = document.querySelectorAll(cls);
  let data = [];
  if(els){
    els.forEach((el,ind) => {
      let cs = getComputedStyle(el);
      data[ind] = cs.getPropertyValue(prop).trim();
      
    });
  }
  return data;
}

console.log(getCSSPropertyData('.foo', '--fg-color'));


setCSSProperty('.foo', '--fg-color', 'green');
.foo {
  --fg-color: red;
}
.bar {
  --fg-color: blue;
}
div {
 color: var(--fg-color);
}
<div class="foo">foo</div>
<div class="bar">foo</div>
Rajpal Singh
  • 307
  • 1
  • 12
  • Nice try! But This doesn't actually work. It's `foo.style.cssText` is setting a variable on an element, not on the CSS rule. Which kind of defeats the point. – gman Jul 03 '22 at 08:20
  • @gman I Agree with you, But! I am just providing you the way, and if you have more than 1 element with the same class then you should use the `querySelectorAll` to get/set the property for each element of the class. – Rajpal Singh Jul 03 '22 at 08:23