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!

WitVault
  • 23,445
  • 19
  • 103
  • 133
  • You may want to look at using a cookie, session or local var – happymacarts Jan 05 '17 at 18:52
  • Possible duplicate of [How to create a – talemyn Jan 05 '17 at 18:54
  • Look at CSS variables, also known as custom properties. –  Jan 05 '17 at 19:02
  • Is it a (fairly) fixed set of variables you'll get back from the ajax call? – Will Jan 05 '17 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 https://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. – Will Jan 05 '17 at 19:07
  • @Will I will be getting style configuration objects via cloud calls, not just colors but other complex styles stored as Json object – WitVault Jan 05 '17 at 19:25
  • Maybe share one of those "style configuration objects"? – Will Jan 05 '17 at 19:30
  • @Will e.g `".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)" },` – WitVault Jan 05 '17 at 19:33
  • @Will I found something like --var just now in css. is that what you were referring to? – WitVault Jan 05 '17 at 19:46

4 Answers4

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.

Community
  • 1
  • 1
talemyn
  • 7,822
  • 4
  • 31
  • 52
  • I do not want to create new ` – WitVault Jan 05 '17 at 19:45
  • @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 ` – talemyn Jan 05 '17 at 20:07
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>
Will
  • 4,075
  • 1
  • 17
  • 28
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>
Will
  • 4,075
  • 1
  • 17
  • 28
0

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