2

I want to generate css dynamically at run time.

Initially I had used sass and defined some variables and was using those variables. But css has to be generated first from the scss. Sass had given me flexibility to use variables and functions but still I was not able to changes them at run time via javascript.

One way was to change the inline styles via javascript but that approach was not completly flexible.

 document.getElementById("myDiv").style.color = "red"; 

I don't want to do above, neither I want to attach any <style> attribute via javascript.

I want to use javascript but not for chaniging each and every style properties. I want to achieve scss like effect using css and javascript but at run time i.e dynamically.

E.g. suppose I got the color information from the ajax call now I want to change the whole theme of website based on that color received immediately without restarting or re-deploying my application.

e.g

as done in scss

.myClass {
           background:$color;
           // Update color value dynamically at run-time
}

Is it even possible or I am thinking in wrong direction!

9
  • You may want to look at using a cookie, session or local var Commented Jan 5, 2017 at 18:52
  • Possible duplicate of How to create a <style> tag with Javascript Commented Jan 5, 2017 at 18:54
  • Look at CSS variables, also known as custom properties. Commented Jan 5, 2017 at 19:02
  • Is it a (fairly) fixed set of variables you'll get back from the ajax call? Commented Jan 5, 2017 at 19:02
  • Depending on how many elements you're looking to change, and how programmatically related your spacing and various colors are, CSS variables and CSS Filters css-tricks.com/almanac/properties/f/filter might take you a long way. Use JS to update the variable(s) on ::root and rest should cascade. Commented Jan 5, 2017 at 19:07

4 Answers 4

1

Wound up playing with this and CSS variables. I'm adding a second answer because it's very different method from my first answer and it better aligns with your original question (updating CSS variables with JS).

BUT... don't do this. :) Browser support in IE < Edge doesn't exist and it is almost certainly slower than updating an on-page <style> element though I haven't tested it. This jsperf tests various style update methods. It doesn't include innerHTML on a single style element (likely the fastest) but you can see that the following CSS DOM methods are slower than the rest.

// get the stylesheet
// array position depends on how many style sheets you're loading.     
// adjust as needed.
var sheet = document.styleSheets[0];


// simplest method: insertRule()
// setTimeout only for demo so you can see the change
window.setTimeout(function(){
  // @media all {} is a trick to insert more than one 
  // selector and/or properties at once. Otherwise it's:
  // sheet.insertRule(":root", "--header-color: green"); ...repeat...
  sheet.insertRule("@media all { :root { --header-color: green; --main-color: orange;  } }", 1);
}, 1200);



// SAFER method via addCSSRule. 
// button and getAjaxStyles are just placeholders, obviously
var btn = document.querySelector('button');
btn.addEventListener("click", getAjaxStyles);

function getAjaxStyles() {
  // success callback... break apart the json and update the CSS variables
  addCSSRule(sheet, ":root", "--header-color: orange");
  addCSSRule(sheet, ":root", "--main-color: blue");
  addCSSRule(sheet, ":root", "--alt-color: red");
  addCSSRule(sheet, ":root", "--borderColorA: lavender");
  
  // or go with a single big string. definitely faster:
  // addCSSRule(sheet, ":root", "--alt-color: red; --borderColorA: #0ff; ")
}

// Credit for addCSSRule() goes to Diego Flórez in a comment on 
// https://davidwalsh.name/add-rules-stylesheets
var addCSSRule = function(sheet, selector, rules) {
  //Backward searching of the selector matching cssRules
  var index = sheet.cssRules.length - 1;
  for (var i = index; i > 0; i--) {
    var current_style = sheet.cssRules[i];
    if (current_style.selectorText === selector) {
      //Append the new rules to the current content of the cssRule;
      rules = current_style.style.cssText + rules;
      sheet.deleteRule(i);
      index = i;
    }
  }
  if (sheet.insertRule) {
    sheet.insertRule(selector + "{" + rules + "}", index);
  } else {
    sheet.addRule(selector, rules, index);
  }
  return sheet.cssRules[index].cssText;
}
/* Set initial CSS variables */
:root {
  --header-color: #333;
  --main-color: #888;
  --alt-color: #bbb;
  --borderColorA: #ccc;
}
h1 {
  color: var(--header-color);
}
p {
  border-bottom: 1px solid var(--borderColorA);
  color: var(--main-color);
}
p+p {
  color: var(--alt-color);
}
<h1>header</h1>
<p>paragraph 1</p>
<p>paragraph 2</p>
<button>Update CSS Variables</button>

Sign up to request clarification or add additional context in comments.

1 Comment

It seems quite lots of effort in updating variables. Thanks a lot for your effort and research.
1

To expand on the information that is provided in the linked "possible duplicate" question, you could easily set up a "default" set of styles in your page CSS file and then create a inline <style> ekement containing any overrides based on the response from your AJAX call. As long as the element/class/id definitions are the same in the two locations (i.e., CSS file and inline style section), specificity will cause the inline definitions to override the CSS ones.

So, using your example, your static CSS file would contain:

.myClass {
    background: #FFFFFF;
}

. . . so that there is a default value if the AJAX call were to fail, and then your dynamically created <style> section would contain:

.myClass {
    background: THE_AJAX_RESPONSE_VALUE;
}

. . . which would override the default value.

UPDATE #1:

Based on your sample JSON, this would be REALLY easy . . . you would loop through each top-level property of the JSON and create this:

KEY_NAME {
    . . .
}

Then, within that block, loop through each property within that property and add the keys and values to create the style definitions:

KEY_NAME {
    key1: value1,
    key2: value2,
    . . .
    keyN: valueN
}

UPDATE #2:

You can also use StyleSheet and CSSStyleSheet interfaces to access the rules that are in the existing stylesheets, but, given that it uses an array-like structure, that means looping through all of the CSS definitions to find the one that you want and alter it. An example of how to do that can be found in this answer to another SO question: Is it possible to alter a CSS stylesheet using JavaScript? (NOT the style of an object, but the stylesheet itself)

Between the two approaches, though, creating an overriding <style> section seems like the easier approach.

2 Comments

I do not want to create new <Style> tags via js I want to use existing css and update the placeholders or variables with new values or merge them
@WitVault - Short answer . . . no. That's why things like SASS were created, to add some flexibility like that (though not actually the specific flexibility that you are looking for). I've added a link to another alternative approach that would allow you actually manipulate the stylesheet rules themselves, but I still think the overriding <style> section is the better option.
1

Since the JSON has both the element names and the related styles, refreshing an on page stylesheet (vs inline element styles) would probably be the fastest since it uses innerHTML and only requires a single DOM lookup.

You'll need to loop through your JSON to create CSS compatible strings and then just dump it into the onpage style element. You can append CSS by concatenating the existing innerHTML with the new CSS string. I added an ID to the stylesheet for simplicity but you could also generate the style element when needed.

var StringifiedAjaxStyleObject = "h1 {background-color: #ecc; color: #633}";
var styleSheet = document.getElementById("style-update");

// some additional fake test style returns...
var testStyle1 = "h1 {background-color: #ccc; color: #333}";
var testStyle2 = "h1 {background-color: #667; color: #bbc}";
var testStyle3 = "h1 {background-color: #fee; color: #b00}";

// some fake ajax returns...
window.setTimeout(function() {
  styleSheet.innerHTML = StringifiedAjaxStyleObject;
}, 1000);
window.setTimeout(function() {
  styleSheet.innerHTML = testStyle1;
}, 2000);
window.setTimeout(function() {
  styleSheet.innerHTML = testStyle2;
}, 3000);
window.setTimeout(function() {
  styleSheet.innerHTML = testStyle3;
}, 4000);
/* base styles ... */
h1 {
  padding: 5px;
  background-color: #eef;
  color: #007
}
<!-- empty stylesheet -->
<style id="style-update" type="text/css" rel="stylesheet"></style>

<h1>Hi, mom</h1>
<button>Update Styles<button>

EDIT:

Here's a slightly more real-world version based on the JSON object in your comment. Trigger it via the button.

var styleSheet = document.getElementById("style-update");
var btn = document.querySelector('button');

btn.addEventListener("click", updateStyles);

function updateStyles() {
  var StringifiedAjaxStyleObject
    , newCSS
    , ajaxReturn
  ;

  // ...your ajax method to get the new styles...
  // on success...
  ajaxReturn = {
    ".base": {
      "background-color": "#b83605",
      "border-color": "#543927",
      "color": "gray",
      "text-shadow": "0 -1px 0 rgba(0, 0, 0, 0.15)"
    },
    ".overlay": {
      "background": "rgba(76, 65, 80, 0.2)",
      "color" : "#ddd"
    }
  };
  
  // Convert the object to a string
  newCSS = cssStringFromJson(ajaxReturn);

  // Update the stylesheet
  styleSheet.innerHTML = newCSS;
}


function cssStringFromJson(cssJSON) {
  var styleStr = "",
    i, j;

  for (i in cssJSON) {
    styleStr += i + " {\n"
    for (j in cssJSON[i]) {
      styleStr += "\t" + j + ": " + cssJSON[i][j] + ";\n"
    }
    styleStr += "}\n"
  }

  return styleStr;
}
/* base styles ... */
.base {
  border: 1px solid #ccf;
  background-color: #eef;
  color: #000;
  padding: 15px;
}

.overlay {
  padding: 5px 15px;
  background: rgba(96, 95, 180, 0.2);
}

body {
  font-family: sans-serif;
}

button {
  margin-top: 1em;
  font-size: 1em;
}
<!-- empty stylesheet -->
<style id="style-update" type="text/css" rel="stylesheet"></style>

<div class="base">
  <p>.base</p>
  <div class="overlay">
    <p>.overlay</p>
  </div>
</div>

<button>Update Styles</button>

Comments

0

You can try angular templates. It is going to break your previous sass, but it will work out later.

1 Comment

I am not using angular

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.