I have a less file colours which defines looks like the following:
#black: #000;
#greyDarker: #222;
...etc
And want to be able to access these from within javascript, so for example:
$('body').style('color', 'window.colours.black') // or however I assign them
would work.
Since Less is getting compiled server-side the usual options wouldn't work.
I've started going ahead and writing a grunt task to generate a js file from these less rules however this seems like an inefficient / hacky way to go about it.
Any suggestions on a better approach or tools that could help
You could put some special definitions in your CSS to pass through the definitions. These would only be used to pass the variables and nothing else. You'd need to come up with some convention, here I've used div.less-rule-{rule-name}
For example.
div.less-rule-black {
background-color: #black;
}
div.less-rule-grey-darker {
background-color: #greyDarker;
}
You could then pick these up using the JavaScript API for accessing stylesheets. You could put this in a utility class somewhere. This only needs to be done once when all the stylesheets are loaded.
var rules, rule, i, n, j, m, key;
var lessRules = [];
for (i = 0, n = document.styleSheets.length; i < n; i++) {
rules = document.styleSheets[i].cssRules;
for (j = 0, m = rules.length; j < m; j++) {
rule = rules[j];
if (rules[j].selectorText.indexOf('less-rule') !== -1) {
key = /div.less-rule-(.*)/.exec(rules[j].selectorText)[1];
lessRules[key] = rule.style['background-color'];
}
}
}
You can then access the variables by using the key in the hash.
console.log(lessRules['black']);
console.log(lessRules['grey-darker']);
You can use css variables Like
let StyleVars = document.querySelector(':root');
// Create a function for getting a variable value
function GetStyleVars(Variable = "--color" ) {
// Get the styles (properties and values) for the root
var Style = getComputedStyle(StyleVars);
return(Style.getPropertyValue(Variable));
}
// Create a function for setting a variable value
function ChangeVariableValue(Value,Variable = "--color") {
// Set the value of variable to another Value
StyleVars.style.setProperty(Variable, Value);
}
document.getElementById("A").onclick = function(){
ChangeVariableValue('red');
}
document.getElementById("B").onclick = function(){
ChangeVariableValue('green');
}
document.getElementById("C").onclick = function(){
ChangeVariableValue('black');
}
document.getElementById("D").onclick = function(){
alert(GetStyleVars());
}
:root{
--color : #fff;
}
button{
border :2px solid var(--color);
}
<button id="A">Red</button>
<br>
<button id="B">Green</button>
<br>
<button id="C">Black</button>
<br>
<button id="D">Alert Value</button>
First add a rule using the LESS variable to your CSS.
Then create a dummy element with that class, and examine its getComputedStyle color:
function getLessVariableValue(variable) {
var elt = document.createElement('div');
elt.className = variable+"-variable";
elt.style.display= "none"; // ensure element is not visible
document.body.appendChild(elt); // elt must be in DOM to compute styles
var result = window.getComputedStyle(elt).fontFamily;
document.body.removeChild(elt);
return result;
}
document.writeln(getLessVariableValue('purple'))
/* replace "purple" below with '#purple' etc. */
.purple-variable { font-family: purple; }
We use font-family because it can take any arbitrary string as its value.
Alternatively, you could look through the style rules for the one with .purple-variable as a selector, as #Adam suggests in his answer. To me that seems a bit more involved.
However, this seems like a lot of work to go to, to accomplish--what? There may be better ways to accomplish what you are trying to do.
You can attach each color to a class name in your style rules and then add a particular class to your object to trigger the setting of the desired color. The class names would be defined in your CSS styles and then you just refer to them by string name in your javascript.
CSS:
.black {color: #000;}
.greyDarker {color: #222;}
Javascript:
$('body').addClass('black');
You can also use the CSS color names built into the browser:
$('body').css('color', 'black');
Here's a list of color names built in: http://www.crockford.com/wrrrld/color.html and http://www.cssportal.com/css3-color-names/,
Other than something like this, if you want programmatic access to symbolic names for colors, then you will need to generate your own Javascript definitions for the names and include that in your javascript where you can use those symbols.
Related
I know it is possible to add new CSS classes definitions at runtime through JavaScript. But...
How to change/remove CSS classes definitions at runtime?
For instance, supose a I have the class below:
<style>
.menu { font-size: 12px; }
</style>
What I want is, at runtime, change the font-size rule of the .menu class, so that every element in the page who uses this class will be affected.
And, I also want to know how to remove the .menu class definition.
It's not difficult to change CSS rules at runtime, but apparently it is difficult to find the rule you want. PPK has a quick tour of this on quirksmode.org.
You'll want to use document.styleSheets[i].cssRules which is an array you need to parse through to find the one you want, and then rule.style.setProperty('font-size','10px',null);
I found an answer at http://twelvestone.com/forum_thread/view/31411 and I'm reproducing parts of the thread here, verbatim, because I'm afraid the thread, and the very helpful answer, will evaporate.
Flip 2006.06.26, 02:45PM —
[ Crunchy Frog ]
posts: 2470 join date: 2003.01.26
Well after about 10 to 12 hours of searching, reading, and tinkering I've done it! I am CSS/JS code Ninja today!
The JS code used is as follows:
<script language="JavaScript">
function changeRule(theNumber) {
var theRules = new Array();
if (document.styleSheets[0].cssRules) {
theRules = document.styleSheets[0].cssRules;
} else if (document.styleSheets[0].rules) {
theRules = document.styleSheets[0].rules;
}
theRules[theNumber].style.backgroundColor = '#FF0000';
}
</script>
I've tested this on FF(Mac), Safari(Mac), O9(Mac), IE5(Mac), IE6(PC), FF(PC) and they all work. The reason for the 'if' statement is some of the browsers use cssRules... some use just rules... And the only other hair is that you can't use "background-color" to refer to the style, you have to get rid of the hyphen and capitalize the first letter after the hyphen.
To refer to the first CSS rule you'd use "changeRule(0)", the second "changeRule(1)" and the third "changeRule(2)" and so on...
I haven't found a browser it doesn't work on.... yet....
Anything you say can and will be used against you. Over and over and over.
BillyBones 2011.01.20, 11:57AM —
[ in the barrel ]
posts: 1 join date: 2011.01.20
Hello, I registered in these forums just to add this little bit as I could not conveniently find it elsewhere:
function changeStyle(selectorText)
{
var theRules = new Array();
if (document.styleSheets[0].cssRules) {
theRules = document.styleSheets[0].cssRules;
}
else if (document.styleSheets[0].rules) {
theRules = document.styleSheets[0].rules;
}
for (n in theRules)
{
if (theRules[n].selectorText == selectorText) {
theRules[n].style.color = 'blue';
}
}
}
This simply makes the CSS rule identifiable by its selector name rather than by its index number in the cssRules array.
In other words, you can execute the Javascript function with the string argument "selectorText" instead of a number that is difficult to remember and susceptible to frequent changes if new styles are added.
Thank you for your 10 to 12 hours of research, Flip, I hope I made a worthy addition.
i think you are looking for this:
http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
this lets you change the actual rules with javascript. ive used it once, a few years ago it seemed to have worked.
I've made a simple helper function for anyone that want to do that:
function getCSSRule(search) {
return [].map.call(document.styleSheets, function(item) {
return [].slice.call(item.cssRules);
}).reduce(function(a, b) {
return b.concat(a);
}).filter(function(rule) {
return rule.selectorText.lastIndexOf(search) === rule.selectorText.length - search.length;
})[0];
}
And then, you can use it like that:
getCSSRule('.mydiv').style.fontSize = '20px';
Take a look at the example below:
function getCSSRule(search) {
return [].map.call(document.styleSheets, function(item) {
return [].slice.call(item.cssRules);
}).reduce(function(a, b) {
return b.concat(a);
}).filter(function(rule) {
return rule.selectorText.lastIndexOf(search) === rule.selectorText.length - search.length;
})[0];
}
document.querySelector('button').addEventListener('click', function(e) {
getCSSRule('.iframe').style.backgroundColor = 'orange';
});
.iframe {
height: 200px;
width: 200px;
display: inline-block;
border: 1px solid #000;
}
<p>
<button>Change .iframe background-color</button>
</p>
<div class="iframe"></div>
<div class="iframe"></div>
I took the best of the answers here, and combined them, for ease of use, and cross browser compatibility. Also, I covered when threw errors if no stylesheets were on the page, or if the css rule did not exist yet.
https://github.com/Frazer/dynamicallyAccessCSS.js
Here's an embarrassingly simple trick I've been using for dynamically manipulating CSS class rules, which dodges the complications (like parsing through the rules to locate the one you need, as noted by the expected answer), and even provides some extra flexibility for free.
(A side-note: the prev. attempt of adding this answer got instanty downvoted, within seconds, without feedback, by one of the few auto-notification targets of this page. I'm not sure the short knee-jerk reaction time was enough to see why this is a reliable solution that covers the OP's use case well, nevertheless I've rephrased it, guessing the narrative may have somehow been the trigger. Or the lack of semicolons (which I've now added for him). Or the lack of unnecessary curly braces (also added). Or the lack of notes on non-effects (added). Or not using a sufficiently complicated method (won't fix, even simplified the code yet some more). I'm sure he'll hit again anonymously, but still retry, because this is a clean & robust technique worth using.)
NOTE: This approach assumes you have sufficient control over the styling of the page to make sure the same class rule would not get created by a different method. (And it expects the HEAD element to exist.)
(One potential cost of this approach could be re-rendering on innerHtml, but since we are changing CSS anyway, repaint is imminent. Also, innerHtml is done to a non-displayed element, so browsers could optimize it out even if it otherwise mattered.)
OK. Since you can have any number of STYLE elements, it's perfectly fine to wrap your dynamic class in its own separate one. Um, that's it. :) Then, add an id or, even better*, a class attribute to the wrapper STYLE, so you can practically access and manipulate your class rule as if it was a DOM element. Just make sure it's wrapped in <style class="..."> ... </style>, when adding/replacing.
Bonus: you can also group multiple related rules and replace them all at once this way, if you wish (with trivial modifications):
function replace_class(classname, block) {
// Remove old:
var s = document.head.querySelector("style." + classname);
if (s) { document.head.removeChild(s); }
// Just delete?
if (!block) { return; }
// Add new:
s = document.createElement("style");
s.className = classname;
s.innerHTML = ("." + classname + block); // <style class="classname">.classname{...}</style>
document.head.appendChild(s);
}
Then, to update: replace_class("menu", "{font-size: 8px;}").
Or delete: replace_class("menu", null).
* Since CSS applies to every element, you may wonder why won't STYLE itself get unexpectedly rendered, if your new class had a display: ... with something else than none. Well, it would, if it was put in the BODY! But, since we add it to HEAD, rendering is skipped (unless, of course, you opt to display HEAD, too). Or, you could also use id instead, but then you had to invent/use proper scoping/prefixing, and why would you want that if it can be spared? (And I also like the subtle cheekiness of setting class when it's a class wrapper anyway...)
It is difficult to find the rule you want because you have to iterate through the document.styleSheets[i].cssRules array. (and compare your class name with the selectorText attribute)
So my solution to this problem is to add a new CSS class, remove the old CSS class from the HTML element and add this class instead of it.
var length = getCssRuleLength();
var newClassName = "css-class-name" + length;
//remove preview css class from html element.
$("#your-html-element").removeClass("css-class-name");
$("#your-html-element").removeClass("css-class-name" + (length-1));
$("#your-html-element").addClass(newClassName);
//insert a css class
insertCssRule("." + newClassName + ' { max-width: 100px; }', length);
function getCssRuleLength() {
var length = 0;
if (document.styleSheets[1].cssRules) {
length = document.styleSheets[1].cssRules.length;
} else if (document.styleSheets[1].rules) { //ie
length = document.styleSheets[1].rules.length;
}
return length;
}
function insertCssRule(rule, index) {
if (document.styleSheets[1].cssRules) {
document.styleSheets[1].insertRule(rule, index);
} else if (document.styleSheets[1].rules) { //ie
document.styleSheets[1].addRule(rule, index);
}
}
Below is an approach that will work for any given rule-selector and rule-change function:
// general function for selecting rules and applying changes
function change_css_rules(changeRuleFunc, selectorFunc) {
[].concat.apply([], // flattens arrays
Array.from(document.styleSheets).map(function(sheet) { // each ss
return Array.from(sheet.cssRules).filter(function(rule) { // each rule
return selectorFunc(rule); // only select desired rules
});
})
).map(changeRuleFunc); // change the selected rules
}
Example use:
var my_changeRuleFunc = function(rule) {
rule.style.fontSize = '20px';
}
var my_selectorFunc = function(rule) {
return rule.selectorText == '.myClass'; // return true to select this rule
}
change_css_rules(my_changeRuleFunc, my_selectorFunc); // apply change to selected rules
In some browsers (namely, Firefox) the getComputedStyle().getPropertyValue() doesn't report anything for shorthand CSS, like border. Is there a non-specific-code way of getting these shorthand CSS values? I've considered making a whitelist of shorthand CSS and their respective longhand CSS values. But I realize doing that would be both a big pain and a non-forward-compatible design.
I'm wondering, what do you want to do with a string like border: 1px solid #000?
Say you want to reproduce an elems border in order to copy it copyStyle(el2, el, "border"):
// Copies a set of styles from one element to another.
function copyStyle(dest, source, shorthand) {
var computed = window.getComputedStyle(source, null);
for (var i = computed.length; i--;) {
var property = camelize(computed[i]);
if (property.indexOf(shorthand) > -1) {
console.log(property)
dest.style[property] = computed[property];
}
}
}
// prototype.js
function camelize(text) {
return text.replace(/-+(.)?/g, function (match, chr) {
return chr ? chr.toUpperCase() : '';
});
}
Comparing if two element's given set of styles matches can be done in the same manner. Other than that, I really can't see the use a string, which should be parsed if you want to compute anything with it.
Is it possible to get a value from the external CSS of a page if the element that the style refers to has not been generated yet? (the element is to be generated dynamically).
The jQuery method I've seen is $('element').css('property');, but this relies on element being on the page. Is there a way of finding out what the property is set to within the CSS rather than the computed style of an element?
Will I have to do something ugly like add a hidden copy of the element to my page so that I can access its style attributes?
With jQuery:
// Scoping function just to avoid creating a global
(function() {
var $p = $("<p></p>").hide().appendTo("body");
console.log($p.css("color"));
$p.remove();
})();
p {color: blue}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Using the DOM directly:
// Scoping function just to avoid creating a global
(function() {
var p = document.createElement('p');
document.body.appendChild(p);
console.log(getComputedStyle(p).color);
document.body.removeChild(p);
})();
p {color: blue}
Note: In both cases, if you're loading external style sheets, you'll want to wait for them to load in order to see their effect on the element. Neither jQuery's ready nor the DOM's DOMContentLoaded event does that, you'd have to ensure it by watching for them to load.
Normally you should be let the browser apply all the rules and then ask the browser for the results, but for the rare case where you really need to get the value out of the style sheet you can use this: (JSFiddle)
function getStyleSheetPropertyValue(selectorText, propertyName) {
// search backwards because the last match is more likely the right one
for (var s= document.styleSheets.length - 1; s >= 0; s--) {
var cssRules = document.styleSheets[s].cssRules ||
document.styleSheets[s].rules || []; // IE support
for (var c=0; c < cssRules.length; c++) {
if (cssRules[c].selectorText === selectorText)
return cssRules[c].style[propertyName];
}
}
return null;
}
alert(getStyleSheetPropertyValue("p", "color"));
Note that this is pretty fragile, as you have to supply the full selector text that matches the rule you are looking up (it is not parsed) and it does not handle duplicate entries or any kind of precedence rules. It's hard for me to think of a case when using this would be a good idea, but here it is just as an example.
In response to Karim79, I just thought I'd toss out my function version of that answer. I've had to do it several times so this is what I wrote:
function getClassStyles(parentElem, selector, style){
elemstr = '<div '+ selector +'></div>';
var $elem = $(elemstr).hide().appendTo(parentElem);
val = $elem.css(style);
$elem.remove();
return val;
}
val = getClassStyles('.container:first', 'class="title"', 'margin-top');
console.warn(val);
This example assumes you have and element with class="container" and you're looking for the margin-top style of the title class in that element. Of course change up to fit your needs.
In the stylesheet:
.container .title{ margin-top:num; }
Let me know what you think - Would you modify it, and if so how? Thanks!
I have written a helper function that accepts an object with the css attributes to be retrieved from the given css class and fills in the actual css attribute values.
Example is included.
function getStyleSheetValues(colScheme) {
var tags='';
var obj= colScheme;
// enumerate css classes from object
for (var prop in obj) {
if (obj.hasOwnProperty(prop) && typeof obj[prop]=="object") {
tags+= '<span class="'+prop+'"></span>';
}
}
// generate an object that uses the given classes
tags= $('<div>'+tags+'</div>').hide().appendTo("body");
// read the class properties from the generated object
var idx= 0;
for (var prop in obj) {
if (obj.hasOwnProperty(prop) && typeof obj[prop]=="object") {
var nobj= obj[prop];
for (var nprop in nobj) {
if (nobj.hasOwnProperty(nprop) && typeof(nobj[nprop])=="string") {
nobj[nprop]= tags.find("span:eq("+idx+")").css(nobj[nprop]);
}
}
idx++;
}
}
tags.remove();
}
// build an object with css class names where each class name contains one
// or more properties with an arbitrary name and the css attribute name as its value.
// This value will be replaced by the actual css value for the respective class.
var colorScheme= { chart_wall: {wallColor:'background-color',wallGrid:'color'}, chart_line1: { color:'color'} };
$(document).ready(function() {
getStyleSheetValues(colorScheme);
// debug: write the property values to the console;
if (window.console) {
var obj= colorScheme;
for (var prop in obj) {
if (obj.hasOwnProperty(prop) && typeof obj[prop]=="object") {
var nobj= obj[prop];
for (var nprop in nobj) {
if (nobj.hasOwnProperty(nprop)) {
console.log(prop+'.'+nprop +':'+ nobj[nprop]);
}
}
}
}
// example of how to read an individual css attribute value
console.log('css value for chart_wall.wallGrid: '+colorScheme.chart_wall.wallGrid);
}
});
I wrote this js function, seems to be working for nested classes as well:
usage:
var style = get_css_property('.container-class .sub-container-class .child-class', 'margin');
console.log('style');
function get_css_property(class_name, property_name){
class_names = class_name.split(/\s+/);
var container = false;
var child_element = false;
for (var i = class_names.length - 1; i >= 0; i--) {
if(class_names[i].startsWith('.'))
class_names[i] = class_names[i].substring(1);
var new_element = $.parseHTML('<div class="' + class_names[i] + '"></div>');
if(!child_element)
child_element = new_element;
if(container)
$(new_element).append(container);
container = new_element;
}
$(container).hide().appendTo('body');
var style = $(child_element).css(property_name);
$(container).remove();
return style;
}
For a JavaScript library I'm implementing, I need to clone an element which has exactly the same applied style than the original one. Although I've gained a rather decent knowledge of JavaScript, as a programming language, while developing it, I'm still a DOM scripting newbie, so any advice about how this can be achieved would be extremely helpful (and it has to be done without using any other JavaScript library).
Thank you very much in advance.
Edit: cloneNode(true) does not clone the computed style of the element. Let's say you have the following HTML:
<body>
<p id="origin">This is the first paragraph.</p>
<div id="destination">
<p>The cloned paragraph is below:</p>
</div>
</body>
And some style like:
body > p {
font-size: 1.4em;
font-family: Georgia;
padding: 2em;
background: rgb(165, 177, 33);
color: rgb(66, 52, 49);
}
If you just clone the element, using something like:
var element = document.getElementById('origin');
var copy = element.cloneNode(true);
var destination = document.getElementById('destination');
destination.appendChild(copy);
Styles are not cloned.
Not only will you need to clone, but you'll probably want to do deep cloning as well.
node.cloneNode(true);
Documentation is here.
If deep is set to false, none of the
child nodes are cloned. Any text that
the node contains is not cloned
either, as it is contained in one or
more child Text nodes.
If deep evaluates to true, the whole
subtree (including text that may be in
child Text nodes) is copied too. For
empty nodes (e.g. IMG and INPUT
elements) it doesn't matter whether
deep is set to true or false but you
still have to provide a value.
Edit: OP states that node.cloneNode(true) wasn't copying styles. Here is a simple test that shows the contrary (and the desired effect) using both jQuery and the standard DOM API:
var node = $("#d1");
// Add some arbitrary styles
node.css("height", "100px");
node.css("border", "1px solid red");
// jQuery clone
$("body").append(node.clone(true));
// Standard DOM clone (use node[0] to get to actual DOM node)
$("body").append(node[0].cloneNode(true));
Results are visible here: http://jsbin.com/egice3/
Edit 2
Wish you would have mentioned that before ;) Computed style is completely different. Change your CSS selector or apply that style as a class and you'll have a solution.
Edit 3
Because this problem is a legitimate one that I didn't find any good solutions for, it bothered me enough to come up with the following. It's not particularily graceful, but it gets the job done (tested in FF 3.5 only).
var realStyle = function(_elem, _style) {
var computedStyle;
if ( typeof _elem.currentStyle != 'undefined' ) {
computedStyle = _elem.currentStyle;
} else {
computedStyle = document.defaultView.getComputedStyle(_elem, null);
}
return _style ? computedStyle[_style] : computedStyle;
};
var copyComputedStyle = function(src, dest) {
var s = realStyle(src);
for ( var i in s ) {
// Do not use `hasOwnProperty`, nothing will get copied
if ( typeof s[i] == "string" && s[i] && i != "cssText" && !/\d/.test(i) ) {
// The try is for setter only properties
try {
dest.style[i] = s[i];
// `fontSize` comes before `font` If `font` is empty, `fontSize` gets
// overwritten. So make sure to reset this property. (hackyhackhack)
// Other properties may need similar treatment
if ( i == "font" ) {
dest.style.fontSize = s.fontSize;
}
} catch (e) {}
}
}
};
var element = document.getElementById('origin');
var copy = element.cloneNode(true);
var destination = document.getElementById('destination');
destination.appendChild(copy);
copyComputedStyle(element, copy);
See PPK's article entitled Get Styles for more information and some caveats.
After looking at a couple of good solutions across the WEB, I decided to combine all the best aspects of each and come up with this.
I left my solution in plain super fast Javascript, so that everybody can translate to their latest and great JS flavour of the month.
Representing the vanilla from manilla.....
* #problem: Sometimes .cloneNode(true) doesn't copy the styles and your are left
* with everything copied but no styling applied to the clonedNode (it looks plain / ugly). Solution:
*
* #solution: call synchronizeCssStyles to copy styles from source (src) element to
* destination (dest) element.
*
* #author: Luigi D'Amico (www.8bitplatoon.com)
*
*/
function synchronizeCssStyles(src, destination, recursively) {
// if recursively = true, then we assume the src dom structure and destination dom structure are identical (ie: cloneNode was used)
// window.getComputedStyle vs document.defaultView.getComputedStyle
// #TBD: also check for compatibility on IE/Edge
destination.style.cssText = document.defaultView.getComputedStyle(src, "").cssText;
if (recursively) {
var vSrcElements = src.getElementsByTagName("*");
var vDstElements = destination.getElementsByTagName("*");
for (var i = vSrcElements.length; i--;) {
var vSrcElement = vSrcElements[i];
var vDstElement = vDstElements[i];
// console.log(i + " >> " + vSrcElement + " :: " + vDstElement);
vDstElement.style.cssText = document.defaultView.getComputedStyle(vSrcElement, "").cssText;
}
}
}
None of those worked for me, but I came up with this based on Luigi's answer.
copyStyles(source: HTMLElement, destination: HTMLElement) {
// Get a list of all the source and destination elements
const srcElements = <HTMLCollectionOf<HTMLElement>>source.getElementsByTagName('*');
const dstElements = <HTMLCollectionOf<HTMLElement>>destination.getElementsByTagName('*');
// For each element
for (let i = srcElements.length; i--;) {
const srcElement = srcElements[i];
const dstElement = dstElements[i];
const sourceElementStyles = document.defaultView.getComputedStyle(srcElement, '');
const styleAttributeKeyNumbers = Object.keys(sourceElementStyles);
// Copy the attribute
for (let j = 0; j < styleAttributeKeyNumbers.length; j++) {
const attributeKeyNumber = styleAttributeKeyNumbers[j];
const attributeKey: string = sourceElementStyles[attributeKeyNumber];
dstElement.style[attributeKey] = sourceElementStyles[attributeKey];
}
}
}
I know it is possible to add new CSS classes definitions at runtime through JavaScript. But...
How to change/remove CSS classes definitions at runtime?
For instance, supose a I have the class below:
<style>
.menu { font-size: 12px; }
</style>
What I want is, at runtime, change the font-size rule of the .menu class, so that every element in the page who uses this class will be affected.
And, I also want to know how to remove the .menu class definition.
It's not difficult to change CSS rules at runtime, but apparently it is difficult to find the rule you want. PPK has a quick tour of this on quirksmode.org.
You'll want to use document.styleSheets[i].cssRules which is an array you need to parse through to find the one you want, and then rule.style.setProperty('font-size','10px',null);
I found an answer at http://twelvestone.com/forum_thread/view/31411 and I'm reproducing parts of the thread here, verbatim, because I'm afraid the thread, and the very helpful answer, will evaporate.
Flip 2006.06.26, 02:45PM —
[ Crunchy Frog ]
posts: 2470 join date: 2003.01.26
Well after about 10 to 12 hours of searching, reading, and tinkering I've done it! I am CSS/JS code Ninja today!
The JS code used is as follows:
<script language="JavaScript">
function changeRule(theNumber) {
var theRules = new Array();
if (document.styleSheets[0].cssRules) {
theRules = document.styleSheets[0].cssRules;
} else if (document.styleSheets[0].rules) {
theRules = document.styleSheets[0].rules;
}
theRules[theNumber].style.backgroundColor = '#FF0000';
}
</script>
I've tested this on FF(Mac), Safari(Mac), O9(Mac), IE5(Mac), IE6(PC), FF(PC) and they all work. The reason for the 'if' statement is some of the browsers use cssRules... some use just rules... And the only other hair is that you can't use "background-color" to refer to the style, you have to get rid of the hyphen and capitalize the first letter after the hyphen.
To refer to the first CSS rule you'd use "changeRule(0)", the second "changeRule(1)" and the third "changeRule(2)" and so on...
I haven't found a browser it doesn't work on.... yet....
Anything you say can and will be used against you. Over and over and over.
BillyBones 2011.01.20, 11:57AM —
[ in the barrel ]
posts: 1 join date: 2011.01.20
Hello, I registered in these forums just to add this little bit as I could not conveniently find it elsewhere:
function changeStyle(selectorText)
{
var theRules = new Array();
if (document.styleSheets[0].cssRules) {
theRules = document.styleSheets[0].cssRules;
}
else if (document.styleSheets[0].rules) {
theRules = document.styleSheets[0].rules;
}
for (n in theRules)
{
if (theRules[n].selectorText == selectorText) {
theRules[n].style.color = 'blue';
}
}
}
This simply makes the CSS rule identifiable by its selector name rather than by its index number in the cssRules array.
In other words, you can execute the Javascript function with the string argument "selectorText" instead of a number that is difficult to remember and susceptible to frequent changes if new styles are added.
Thank you for your 10 to 12 hours of research, Flip, I hope I made a worthy addition.
i think you are looking for this:
http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
this lets you change the actual rules with javascript. ive used it once, a few years ago it seemed to have worked.
I've made a simple helper function for anyone that want to do that:
function getCSSRule(search) {
return [].map.call(document.styleSheets, function(item) {
return [].slice.call(item.cssRules);
}).reduce(function(a, b) {
return b.concat(a);
}).filter(function(rule) {
return rule.selectorText.lastIndexOf(search) === rule.selectorText.length - search.length;
})[0];
}
And then, you can use it like that:
getCSSRule('.mydiv').style.fontSize = '20px';
Take a look at the example below:
function getCSSRule(search) {
return [].map.call(document.styleSheets, function(item) {
return [].slice.call(item.cssRules);
}).reduce(function(a, b) {
return b.concat(a);
}).filter(function(rule) {
return rule.selectorText.lastIndexOf(search) === rule.selectorText.length - search.length;
})[0];
}
document.querySelector('button').addEventListener('click', function(e) {
getCSSRule('.iframe').style.backgroundColor = 'orange';
});
.iframe {
height: 200px;
width: 200px;
display: inline-block;
border: 1px solid #000;
}
<p>
<button>Change .iframe background-color</button>
</p>
<div class="iframe"></div>
<div class="iframe"></div>
I took the best of the answers here, and combined them, for ease of use, and cross browser compatibility. Also, I covered when threw errors if no stylesheets were on the page, or if the css rule did not exist yet.
https://github.com/Frazer/dynamicallyAccessCSS.js
Here's an embarrassingly simple trick I've been using for dynamically manipulating CSS class rules, which dodges the complications (like parsing through the rules to locate the one you need, as noted by the expected answer), and even provides some extra flexibility for free.
(A side-note: the prev. attempt of adding this answer got instanty downvoted, within seconds, without feedback, by one of the few auto-notification targets of this page. I'm not sure the short knee-jerk reaction time was enough to see why this is a reliable solution that covers the OP's use case well, nevertheless I've rephrased it, guessing the narrative may have somehow been the trigger. Or the lack of semicolons (which I've now added for him). Or the lack of unnecessary curly braces (also added). Or the lack of notes on non-effects (added). Or not using a sufficiently complicated method (won't fix, even simplified the code yet some more). I'm sure he'll hit again anonymously, but still retry, because this is a clean & robust technique worth using.)
NOTE: This approach assumes you have sufficient control over the styling of the page to make sure the same class rule would not get created by a different method. (And it expects the HEAD element to exist.)
(One potential cost of this approach could be re-rendering on innerHtml, but since we are changing CSS anyway, repaint is imminent. Also, innerHtml is done to a non-displayed element, so browsers could optimize it out even if it otherwise mattered.)
OK. Since you can have any number of STYLE elements, it's perfectly fine to wrap your dynamic class in its own separate one. Um, that's it. :) Then, add an id or, even better*, a class attribute to the wrapper STYLE, so you can practically access and manipulate your class rule as if it was a DOM element. Just make sure it's wrapped in <style class="..."> ... </style>, when adding/replacing.
Bonus: you can also group multiple related rules and replace them all at once this way, if you wish (with trivial modifications):
function replace_class(classname, block) {
// Remove old:
var s = document.head.querySelector("style." + classname);
if (s) { document.head.removeChild(s); }
// Just delete?
if (!block) { return; }
// Add new:
s = document.createElement("style");
s.className = classname;
s.innerHTML = ("." + classname + block); // <style class="classname">.classname{...}</style>
document.head.appendChild(s);
}
Then, to update: replace_class("menu", "{font-size: 8px;}").
Or delete: replace_class("menu", null).
* Since CSS applies to every element, you may wonder why won't STYLE itself get unexpectedly rendered, if your new class had a display: ... with something else than none. Well, it would, if it was put in the BODY! But, since we add it to HEAD, rendering is skipped (unless, of course, you opt to display HEAD, too). Or, you could also use id instead, but then you had to invent/use proper scoping/prefixing, and why would you want that if it can be spared? (And I also like the subtle cheekiness of setting class when it's a class wrapper anyway...)
It is difficult to find the rule you want because you have to iterate through the document.styleSheets[i].cssRules array. (and compare your class name with the selectorText attribute)
So my solution to this problem is to add a new CSS class, remove the old CSS class from the HTML element and add this class instead of it.
var length = getCssRuleLength();
var newClassName = "css-class-name" + length;
//remove preview css class from html element.
$("#your-html-element").removeClass("css-class-name");
$("#your-html-element").removeClass("css-class-name" + (length-1));
$("#your-html-element").addClass(newClassName);
//insert a css class
insertCssRule("." + newClassName + ' { max-width: 100px; }', length);
function getCssRuleLength() {
var length = 0;
if (document.styleSheets[1].cssRules) {
length = document.styleSheets[1].cssRules.length;
} else if (document.styleSheets[1].rules) { //ie
length = document.styleSheets[1].rules.length;
}
return length;
}
function insertCssRule(rule, index) {
if (document.styleSheets[1].cssRules) {
document.styleSheets[1].insertRule(rule, index);
} else if (document.styleSheets[1].rules) { //ie
document.styleSheets[1].addRule(rule, index);
}
}
Below is an approach that will work for any given rule-selector and rule-change function:
// general function for selecting rules and applying changes
function change_css_rules(changeRuleFunc, selectorFunc) {
[].concat.apply([], // flattens arrays
Array.from(document.styleSheets).map(function(sheet) { // each ss
return Array.from(sheet.cssRules).filter(function(rule) { // each rule
return selectorFunc(rule); // only select desired rules
});
})
).map(changeRuleFunc); // change the selected rules
}
Example use:
var my_changeRuleFunc = function(rule) {
rule.style.fontSize = '20px';
}
var my_selectorFunc = function(rule) {
return rule.selectorText == '.myClass'; // return true to select this rule
}
change_css_rules(my_changeRuleFunc, my_selectorFunc); // apply change to selected rules