How do you get/set non root CSS variables from JavaScript - javascript

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?

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>

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.

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>

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>

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.

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");

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>

Related

How do I change the innerHTML of a global style element with cssRule?

This doesn't really require much content as the code itself is self explanatory. However, just have to escape the condition of writing this context. How
do I update the innerHTML of a style element by changing the cssText of the stylesheet's cssRule?
HTML:
<style>
body {
background-color: blue;
}
</style>
JS:
var style = document.getElementsByTagName('style')[0];
var sheet = style.sheet;
sheet.cssRules[0].style.backgroundColor = 'green'; // body changes to green.
sheet.cssRules[0].cssText; // returns "body { background-color: green; }"
style.innerHTML; // returns "body { backgrounds color: blue; }"
How do I change the innerHTML of the style along with the cssRule?
HTML markup is usually meant to be markup, not dynamically-changing non-markup data, as you're using it for in the dynamic stylesheet. If you want the innerHTML to change, you'll have to collect the new text of all the cssRules and explicitly reassign the innerHTML of the style element.
(similarly, if you reassign a variable inside a script tag, the innerHTML of that script tag doesn't change:
let foo = 5;
foo = 7;
console.log(document.currentScript.innerHTML);
)
<style data-st="st">
body {
background-color: blue;
}
</style>
<script>
var style = document.querySelector('style[data-st="st"]');
var sheet = style.sheet;
sheet.cssRules[0].style.backgroundColor = 'green';
const newFullText = [...sheet.cssRules]
.map(({ cssText }) => cssText)
.join('\n');
style.innerHTML = newFullText;
console.log(style.innerHTML);
</script>
Note that you must use sheet.cssRules to get a collection of rules; sheet.cssRule will evaluate to undefined.
Because you're retrieving and inserting text into the inside of the style tag, rather than HTML markup, it might be more appropriate to use textContent rather than innerHTML:
<style data-st="st">
body {
background-color: blue;
}
</style>
<script>
var style = document.querySelector('style[data-st="st"]');
var sheet = style.sheet;
sheet.cssRules[0].style.backgroundColor = 'green';
const newFullText = [...sheet.cssRules]
.map(({ cssText }) => cssText)
.join('\n');
style.textContent = newFullText;
console.log(style.textContent);
</script>

How get all computed css properties of element and its children in Javascript

I'm trying to set up a service in python using pdfKit to create a pdf file from html files.
So basically I will send my element as string and expect the server to return a pdf version of it, but to be an accurate representation I also need to send a css file of the element.
How can I do this? Generate JSON / object with only the relevant style properties and selectors of an element and all its children. Respecting hierarchy and no duplicates. There are similar questions but they are outdated and tend to not consider children elements.
I was thinking maybe there is a way to create a new DOM from this element and then get the root css?
Here is something I came up with, basically pass the element you want to extract the styles of and ones of its children, and it will return you the stylesheet as a string. Open your console before running the snippet and you will see the output from the console.log.
Because I wanted to support the extraction of every element even those without a selector, I had to replace each element id by a unique uuid specifically generated for them in order to facilitate the styling of your output. The problem with this approach is in case you are using ids for styling or for user interaction, you are going to loose such functionality on concerned elements after calling extractCSS.
However, it is pretty trivial to use the oldId I'm passing to change back once your pdfKit process finished the generation. Simply call swapBackIds passing the elements returned by the function. You can see the difference of behavior if you uncomment the call in my snippet: the #root pink background would disappear because the styling targets an element id.
All in all, you need to:
Call extractCSS with the element you want to extract
Generate your pdf using res.stylesheet
Call swapBackIds with res.elements
// Generate an unique id for your element
// From https://stackoverflow.com/a/2117523/2054072
function uuidv4 () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// Flatten an array
// https://stackoverflow.com/a/15030117/2054072
function flatten(arr) {
return arr.reduce(function (flat, toFlatten) {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}
function recursiveExtract (element) {
var id = uuidv4()
var oldId = element.id
var computed = window.getComputedStyle(element)
var style = computed.cssText
// Now that we get the style, we can swap the id
element.setAttribute('id', id)
// The children are not a real array but a NodeList, we need to convert them
// so we can map over them easily
var children = Array.prototype.slice.call(element.children)
return [{ id: id, style: style, oldId: oldId }].concat(children.map(recursiveExtract))
}
function extractCSS (element) {
if (!element) { return { elements: [], stylesheet: '' } }
var raw = recursiveExtract(element)
var flat = flatten(raw)
return {
elements: flat,
stylesheet: flat.reduce(function (acc, cur) {
var style = '#' + cur.id + ' {\n' + cur.style + '\n}\n\n'
return acc + style
}, '')
}
}
var pdfElement = document.querySelector('#root')
var res = extractCSS(pdfElement)
console.log(res.stylesheet)
function swapBackIds (elements) {
elements.forEach(function (e) {
var element = document.getElementById(e.id)
element.setAttribute('id', e.oldId)
})
}
swapBackIds(res.elements)
#root {
background-color: pink;
}
.style-from-class {
background-color: red;
width: 200px;
height: 200px;
}
.style-from-id {
background-color: green;
width: 100px;
height: 100px;
}
<div id="root">
<span>normal</span>
<span style="background: blue">inline</span>
<div class="style-from-class">
style-class
</div>
<div class="style-from-id">
style-id
<div style="font-size: 10px">a very nested</div>
<div style="font-size: 12px; color: white">and another</div>
</div>
</div>
<div id="ignored-sibling">
</div>
let para = document.querySelector('p');
let compStyles = window.getComputedStyle(para);
para.textContent = 'My computed font-size is ' + compStyles.getPropertyValue('font-size') + ',\nand my computed background is ' + compStyles.getPropertyValue('background') + '.';
p {
width: 400px;
margin: 0 auto;
padding: 20px;
font: 2rem/2 sans-serif;
text-align: center;
background: purple;
color: white;
}
<p>Hello</p>
you can use getComputedStyle method to get computed value of style property

Retrieve CSS percentage value with jQuery

Currently the width() method and all of it's variations in jQuery return pixel values. The same happens when calling the css('width') method.
I have elements, which are styled in .css files, and I have no way of knowing if they're styled in percentages or pixels, but in case it's in percentage or no width is explicitly set on the element, I need to get a percent value.
For example if I have the following:
.seventy { width: 70%; }
.pixels { width: 350px; }
<div class="seventy"></div>
<div class="pixels"></div>
<div class="regular"></div>
I would need these results.
$('.seventy').method() //=> '70%'
$('.pixels').method() //=> '350px'
$('.regular').method() //=> '100%' since that's how block elements behave
Is there anything in jQuery I can use to achieve this effect? Or a custom approach to it?
You can parse the document.stylesheets to find a match. Note, this will only return the actual style after the browser has parsed it so is of no use for getting raw unadulterated CSS as written in file. For that you'd need to parse the file itself rather than the document.stylesheets.
This code is old and untested so your mileage may vary. I have no idea how well it performs with inherited values or more complicated selectors.
//TEST PARSE CSS
var CSS = function () {
var _sheet;
var _rules;
function CSS() {
_sheet = document.styleSheets[0];
if (_sheet.rules) {
_rules = _sheet.rules; // IE
} else {
_rules = _sheet.cssRules; // Standards
}
this.find = function (selector) {
var i = _rules.length;
while(i--){
if (_rules[i].selectorText == selector) {
break;
}
if(i==0){break;}
}
//return _rules[i].cssText;
return _rules[i].style;
}
this.set = function (foo) {
//to do
}
};
return new CSS();
};
//init
var css = new CSS();
//view the console.
console.log(css.find(".regular"));//Note how the width property is blank
//update elements with the results
document.querySelector(".seventy").innerHTML = css.find(".seventy").width;
document.querySelector(".pixels").innerHTML = css.find(".pixels").width;
document.querySelector(".regular").innerHTML = css.find(".regular").width;
//other tests
document.getElementById("a").innerHTML = css.find("body").color;
document.getElementById("b").innerHTML = css.find("h1").color;
document.getElementById("c").innerHTML = css.find("h1").width;
document.getElementById("d").innerHTML = css.find(".notInDom").color;
body {
font-family:sans-serif;
color:black;
background-color:#cccccc;
}
h1 {
color:blue;
font-size:1.5em;
font-weight:400;
width:70%;
}
.seventy, .pixels, .regular {display:block; border:1px solid red;}
.seventy {display:block; border:1px solid red; width: 70%; }
.pixels { width: 350px; }
.regular {}
.notInDom {
color:red;
}
<h1>Find and Read Style Attributes Directly from the Stylesheet.</h1>
<div class="seventy"></div>
<div class="pixels"></div>
<div class="regular"></div>
<ul>
<li>css.find("body").color = <span id='a'></span></li>
<li>css.find("h1").color = <span id='b'></span></li>
<li>css.find("h1").width = <span id='c'></span></li>
<li>css.find(".notInDom").color = <span id='d'></span></li>
</ul>
<p>This is a work in progress and hasn't been tested in any meaningful way. Its messy and very limited.</p>
function getStyle(className) {
var classes = document.styleSheets[0].rules || document.styleSheets[0].cssRules;
for (var x = 0; x < classes.length; x++) {
if (classes[x].selectorText == className) {
(classes[x].cssText) ? alert(classes[x].cssText) : alert(classes[x].style.cssText);
}
}
}
getStyle('.test');

javascript .style property of span tag in anchor tag not working as expected

I am trying to style some buttons for my website
This is my html
<div>
<a class="page_numbers"><span>100</span></a>
<a class="page_numbers"><span>2</a></span></div>
this is my css
.page_numbers{
display:table-cell;
border:solid;
padding:0px;
border-radius:100px;
width:50px;
height:50px;
text-align:center;
vertical-align:middle;
}
div {
display:table;
border-spacing:10px;
}
}
and finally this is my javascript
var obj=document.getElementsByClassName("page_numbers")
for (i in obj){
console.log(obj[i].children)
obj[i].children[0].style.color="black"
obj[i].style.borderColor="rgb(85,170,255)"
function jun(i){
obj[i].addEventListener('mouseenter',function(){obj[i].style.background="yellow";obj[i].style.color="red"},true)
//
obj[i].addEventListener('mouseleave',function(){
obj[i].style.background="white";
obj[i].style.color="rgb(12,31,22)";},true)
}
jun(i);
}
the background color changes on mouseleave and enter but not the font color...I suppose I am doing something wrong along the way or I am missing a fundamental concept
this is my jsfiddle link
http://jsfiddle.net/repzeroworld/boqv8hak/
advice please..still learning JS
Firstly, all of this should be in CSS and is trivial to do so
.page_numbers:hover
{
background-color: yellow;
}
.page_numbers:hover span
{
color: red;
}
Now the issue you are having is that on about the 4th line of your JS you explicitly set the color of the child element (the span) inside the .page_number element to be black. Now on you mouse enter you are setting the color on the page_number element, but since the child has a style applied directly to it (i.e. color: black) it does not inherit the parent style. Inline styles (i.e. style applied directly to the element with the style="" attribute, which is what JS does) always have the highest precedence. This is why it is generally not best practice to put inline styles on an element, as you have just seen, they are pretty much impossible to override. So change either the child to not have an explicit style, or on the mouse enter change the child not the parent
var obj = document.getElementsByClassName("page_numbers")
for (i in obj) {
console.log(obj[i].children)
obj[i].children[0].style.color = "black"
obj[i].style.borderColor = "rgb(85,170,255)"
function jun(i) {
obj[i].addEventListener('mouseenter', function () {
obj[i].style.background = "yellow";
obj[i].children[0].style.color = "red"
}, true)
//
obj[i].addEventListener('mouseleave', function () {
obj[i].style.background = "white";
obj[i].children[0].style.color = "rgb(12,31,22)";
}, true)
}
jun(i);
}
or
var obj = document.getElementsByClassName("page_numbers")
for (i in obj) {
console.log(obj[i].children)
obj[i].style.borderColor = "rgb(85,170,255)"
function jun(i) {
obj[i].addEventListener('mouseenter', function () {
obj[i].style.background = "yellow";
obj[i].style.color = "red"
}, true)
//
obj[i].addEventListener('mouseleave', function () {
obj[i].style.background = "white";
obj[i].style.color = "rgb(12,31,22)";
}, true)
}
jun(i);
}
but as I indicated all this should really be in CSS
You trying to change color of a instead of span
Try like this
obj[i].children[0].style.color = "red"
JSFIDDLE

Is there any way to reset :after/:before CSS rules for an element?

Is there any way to (robustly) reset any possible :after and :before CSS rules for a newly created element?
Usually you can just set the style rules you want to reset on the element directly (with !important if you want to be sure), but I don't know of any way of changing rules defined in :after on the element only.
(Only has to work with Chrome, if at all possible.)
An example at jsFiddle.
The content added with the :before/:after rules is affecting the value returned by clientHeight.
There is a DOM2 API for that matter. The correct way to do this is
document.getOverrideStyle(p, ':after').display = 'none'; // or
document.getOverrideStyle(p, ':after').cssText = 'display: none !important;';
Unfortunately, no browser has implemented it. (Webkit returns null, Firefox has no such method). It looks like CSS3 doesn't even bother talking about that anymore, maybe because the usecases are very rare.
So you're gonna have to do some id/className magic as suggested above or in the other thread
I'd just assign a class name to the new elements that does not have :before / :after content.
Example - http://jsfiddle.net/84kZK/1/
Ah, okay. You can write new CSS that resets the offending :before/:after pseudo-elements:
function resetPsuedo(el) {
if (!el.id) el.id = makeId();
var selector = "#" + el.id;
var head = document.getElementsByTagName('head')[0],
style = document.createElement('style'),
rules = document.createTextNode(selector + ":before, " + selector + ":after { content: '' }");
style.type = 'text/css';
if(style.styleSheet)
style.styleSheet.cssText = rules.nodeValue;
else style.appendChild(rules);
head.appendChild(style);
}
function makeId() {
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
for (var i=0; i < 15; i++)
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
​Assigning a random ID to the element you pass in (if it doesn't have one) allows you to hack-up inline styles—rather than accessing el.beforeStyle, you can use CSS selectors: el#rkhjr828t9g:before.
You may need to add more rules to fully reset the styles. jsFiddle: view me!
http://www.w3.org/TR/CSS21/generate.html#before-after-content
​The :before and :after pseudo-elements interact with other boxes as
if they were real elements inserted just inside their associated
element.
For example, the following document fragment and style sheet:
<p> Text </p> p:before { display: block; content: 'Some'; }
...would render in exactly the same way as the following document
fragment and style sheet:
<p><span>Some</span> Text </p> span { display: block }
Similarly, the following document fragment and style sheet:
<h2> Header </h2> h2:after { display: block; content: 'Thing'; }
...would render in exactly the same way as the following document
fragment and style sheet:
<h2> Header <span>Thing</span></h2> h2 { display: block; }
span { display: block; }
Use ruleSelector("ref::before")[0].style instead of document.getOverrideStyle(ref, ':before').
http://jsfiddle.net/s3fv8e5v/4/
<!DOCTYPE html>
<title>CSS</title>
<style>
body {
font: 200%/1.45 charter;
}
ref::before {
content: '\00A7';
letter-spacing: .1em;
}
</style>
<article>The seller can, under Business Law <ref>1782</ref>, offer a full refund to buyers. </article>
<script>
function ruleSelector(selector) {
function uni(selector) {
return selector.replace(/::/g, ':') // for Firefox
}
return Array.prototype.filter.call(Array.prototype.concat.apply([], Array.prototype.map.call(document.styleSheets, function(x) {
return Array.prototype.slice.call(x.cssRules);
})), function(x) {
return uni(x.selectorText) === uni(selector);
});
}
var toggle = false,
pseudo = ruleSelector("ref::before").slice(-1);
document.querySelector("article").onclick = function() {
pseudo.forEach(function(rule) {
if (toggle = !toggle)
rule.style.color = "red";
else
rule.style.color = "black";
});
}
</script>

Categories

Resources