Change CSS Using JavaScript

Change CSS Using JavaScript

Modify CSS through JavaScript

ยท

9 min read

In the ever-evolving landscape of web development, creating visually appealing and responsive websites is paramount. The way we present information on the web plays a pivotal role in engaging our audience and delivering a memorable user experience. Cascading style sheets (CSS) are the backbone of web styling. When you want to access and modify CSS styles programmatically, JavaScript is the tool of choice.

StyleSheet

ThestyleSheets read-only property of the Document interface returns a StyleSheetList of stylesheet objects, such as stylesheets explicitly linked to or embedded in a document.

const styleSheets = document.styleSheets;
/** once you access it , you have all the informations about any styles
that are applied to the document in the head section **/

<style> .box { background:yellow} </style> 
<link href="style.css" ref="stylesheet" /> 
<!-- is considered as CSSStyleSheet -->


<div style="background-color:red" >
 this is not a CSSStyleSheet
</div>

stylesheet is an object-like array

// We get all the styleSheets;
const styleSheets = document.styleSheets;

const styleSheetsArray = Array.from(styleSheets);

const targetStyleSheet=styleSheetsArray.find((styleSheet)=>{
    if(styleSheet?.href) {
        const pathName = new URL(styleSheet.href).pathName.split("/").pop();
        if(pathName === "extra.css") return styleSheet;
    }
})
// If we have a url to download a styleShet from server
// https://example/style.css => style.css is the name of the file

Disable a style sheet using JS

This can be useful in several use cases: Let's say we have a component that has not loaded yet. Then we don't need to have their style sheet as well, or if we have a different color scheme for our website, the users can choose which one they want to see

targetStyleSheet.disabled = true;
/** Note that styleSheet's been downloaded by the browser but not apply

Insert and delete rules from a style sheet.

This is a very common situation. I would say that this is how CSS IN JS works under the hook that we use JS to add CSS in run-time

๐Ÿ’ก
Every CSS selector we add to the stylesheet is called CSSRules
// Access a stylesheet object (e.g., the first stylesheet on the page)
const styleSheet = document.styleSheets[0];

// Insert a new CSS rule
const ruleIndex = styleSheet.insertRule('p { color: red; }', 0);
// This will add the CSS rule at index 0 remember CSS cascading
// is important for the browsers

// Delete a CSS rule
styleSheet.deleteRule(ruleIndex);

Stylesheet switcher

Let's implement a Stylesheet switcher with JS when we have two buttons to toggle

const styleSheets = document.styleSheets;
const listButton = document.getElementById("list");
const gridButton = document.getElementById("grid");

const getCSSStyleSheet = (name)=>{
  const styleSheetArray =Array.from(styleSheets);
  const styleSheet = styleSheetArray.find((styleSheet)=>{
    if(styleSheet?.href){
      const pathName= new URL(styleSheet.href).pathname.split("/").pop();
      if(pathName === name) {
        return styleSheet;
      }
    }
  })
  return styleSheet;
}

const gridStyleSheet=getCSSStyleSheet('grid.css');

document.addEventListener('DOMContentLoaded',()=>{
  gridStyleSheet.disabled=true;
  listButton.classList.add("current");
})

const toggleButton = (event)=>{
  const id = event.target.id;
  if(id === "list") {
    gridButton.classList.remove("current");
    listButton.classList.add("current");
    gridStyleSheet.disabled=true;
  } else {
    listButton.classList.remove("current");
    gridButton.classList.add("current");
    gridStyleSheet.disabled=false;
  }
}

listButton.addEventListener('click',toggleButton);
gridButton.addEventListener('click',toggleButton);

Computed style

  • internal link to CSS

  • style element

  • internal style of an element

The Window.getComputedStyle() method returns an object containing the values of all CSS properties of an element, after applying active stylesheets and resolving any basic computation, those values may contain

const heading = document.querySelector(".heading");
const computedHeading = window.getComputedStyle(heading);
// we will get an array like object with every style 
// has been applied to it

We can also access the property with CSS-style

computedHeading.fontSize or
computedHeading.getPropertyValue('font-size');

Query an element and apply style to it

๐Ÿ’ก
This style property comes from EventTarget>Node>Element>HTMLElement
const headingItem = document.querySelector(".heading");
headingItem.style.backgroundColor = "blue"

/* Or */
headingItem.style.setProperty("color","red");

Style is also an HTML attribute

If we take a look at our HTML declaration, we will notice that, for example,

<h1 style="background-color:blue;">Hello</h1>

so we can also access the style object as an HTML attribute

headingItem.getAttribute("style");
/** This is not the recommended way to access style property **/
headingItem.setAttribute("style","font-style:italic");
/** This will override the entire style **/
๐Ÿ’ก
I suggest not overriding the HTML attribute style

Working with CSS property

CRUD operators to the stylesheet with JS

const styleSheets = document.styleSheets[0];
const cssRule = styleSheets.cssRules[2].cssText;
// Access a first style document and a first css selector in stylesheet
// In this case we will access .masthead from this stylesheet

What if we want to change max-inline-size in .masthead to a different value?

// with JS style (camel-Case) this won't work with CSS custom property
styleSheets.cssRules[2].style.maxInlineSize = "65rem"
// with CSS style (kebab-style)
styleSheets.cssRules[2].style.setProperty("max-inline-size","65rem");

What if we want to remove individual CSS styles?

styleSheets.cssRules[2].style.removeProperty("margin-inline");

CSS Typed Object Model

If you ever update a CSS property through JS, you use the CSSObjectModel but everything returns a string

//Convert string to number
const headingOpacity = 
   parseFloat(document.styleSheets[0].cssRules[2].style.opacity);

const newOpacity = headingOpacity + 1;
document.styleSheets[0].cssRules[2].style.opacity = newOpacity;

//New way of (its API treat type of CSS data as correct format)
const headingOpacity = document.stylesheets[0].
                        cssRules[2].styleMap.
                        get('opacity').value;
const addHeadingOpacity = headingOpacity + 1;
๐Ÿ’ก
Note that CSSTypeObjectModel has an API-like map data structure in JS. It has get, set, delete, has, clear, etc

Create and access style rules with styleMap property

const styleSheet = document.styleSheets[0];

let maxInlineValue = styleSheet.cssRules[2].styleMap.get("max-inline-size").value;
let maxInlineUnit = styleSheet.cssRules[2].styleMap.get("max-inline-size").unit;

console.log({maxInlineValue,maxInlineUnit});
//70 vw

styleSheet.cssRules[2].styleMap.set("max-inline-size",
         `${max-InlineValue * 2}${max-InlineUnit}`);

Create or access inline styles with attributeStyleMap

const listItem = document.querySelector("heading");

listItem.attributeStyleMap.set("font-size","4rem");
listItem.attributeStyleMap.get("font-size").value; // 4
listItem.attributeStyleMap.get("font-size").unit; // rem

listItem.attributeStyleMap.get("background-color"); 
// because background-color can be either hex or string 

// correct solution is to use the old API 
listItem.style.backgroundColor;
๐Ÿ’ก
If we take notice that the only way we want to change the style of an element is through the style API, it has a very similar shape.
  • style.setProperty("color", "red");

  • style.attributeStyleMap.set("color","red");

  • document.styleSheet[0].cssRules[2].style.setProperty("color","red");

  • document.styleSheet[0].cssRules[2].styleMap.set("color","red");

More on this post https://developer.chrome.com/docs/css-ui/cssom

Dealing with CSS values

๐Ÿ’ก
CSS values are the hardest things. It can be number string or a function type

An important point to consider when setting style values is the assignment of numeric values that represent pixels.

element.style.height = "10px"

element.style.height = 10 + "px"

You might think this is true across all of the style properties but it ain't true

  • z-index

  • font-weight

  • opacity

  • zoom

  • line-height

And transform is another hard-case; it's value is function name

transform:translateX(15px);

Working with Custom Properties

Custom properties (sometimes referred to as CSS variables or cascading variables) are entities defined by CSS authors that contain specific values to be reused throughout a document. They are set using custom property notation (e.g., --main-color: black;) and accessed using the var() function (e.g., color: var(--main-color);).

Complex websites have very large amounts of CSS, often with a lot of repeated values. For example, the same color might be used in hundreds of different places, requiring global search and replacement if that color needs to change. Custom properties allow a value to be stored in one place and then referenced in multiple other places. An additional benefit is semantic identifiers. For example, --main-text-color is easier to understand than#00ff00, especially if this same color is also used in other contexts.

Access Custom property

//Access CSS custom property through JS 
// with Inline Style
const item = document.querySelector(".text");
item.style.getPropertyValue("--color");
// Note that item.style.--color will not work because JS will not 
// understand -- we have to use getPropertyValue

// with StyleSheets
const styleSheet = document.styleSheets[2];
styleSheet.cssRules[0].style.getPropertyValue("--color");

Set Custom property

// with InlineStyle
const item = document.querySelector(".text");
item.style.setProperty("--color","blue");

// with StyleSheets
const styleSheet = document.styleSheets[2];
styleSheets.cssRules[0].style.setProperty("--fontSize","15rem");

Registering a custom property

Without registering a custom property for browsers, some of the advanced techniques like transition and animation won't work as expected

h1 {
  --font-size:4rem;
  font-size:var(--font-size);
  transition: font-size 1s linear;
}

h1:hover {
  --font-size:6rem;
}

/** Technically speaking custom CSS property can be anything browsers
need to know what kind of CSS values that we are offering so browsers
can be well-prepared to do some works for us **/

Registering a custom property allows you to tell the browser how the custom property should behave: what are the allowed types, whether the custom property inherits its value, and what the default value of the custom property is. There are two ways to register a property: in JavaScript or CSS.

window.CSS.registerProperty({
  name: "--font-size",
  syntax: "<length>",
  inherits: false,
  initialValue: "16px", => it has to be a fixed number (px)
});
/** initialValue is the value that first loaded into the browser 
before anything comes into the browser , so rem and em is based on the
how the users set their preference size on the browsers , 
so its not avalid value

Build a Color Picker with CSS variables and JS

  <div class="color-swatch">hsla(0, 100%, 50%, 1);</div>
      <div class="controls">
        <h2>Color controls</h2>
        <div class="hue">
          <label for="hue">Hue:</label>
          <div class="reference"></div>
          <input type="range" id="hue" name="hue" min="0" max="360" value="0" />
        </div>
        <div class="saturation">
          <label for="saturation">Saturation:</label>
          <div class="reference"></div>
          <input
            type="range"
            id="saturation"
            name="saturation"
            min="0"
            max="100"
            value="100"
          />
        </div>
        <div class="lightness">
          <label for="lightness">Lightness:</label>
          <div class="reference"></div>
          <input
            type="range"
            id="lightness"
            name="lightness"
            min="0"
            max="100"
            value="50"
          />
        </div>
        <div class="alpha">
          <label for="alpha">Alpha:</label>
          <div class="reference"></div>
          <input
            type="range"
            id="alpha"
            name="alpha"
            min="0"
            max="100"
            value="100"
          />
        </div>
:root {
  --max-width: 70vw;
  --whitespace: 2rem;
  --hue: 0;
  --saturation: 100;
  --lightness: 50;
  --alpha: 1;
}

* {
  box-sizing: border-box;
}

body {
  margin: 1rem;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
    Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}

.masthead {
  margin-block: var(--whitespace);
  margin-inline: auto;
  max-inline-size: var(--max-width, 70vw);
  display: flex;
  justify-content: center;
}

.masthead h1 {
  font-size: 4rem;
}

.main-content {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.color-swatch {
  width: 100%;
  max-width: 60rem;
  margin-block-end: 1rem;
  padding: 4rem 2rem;
  border: 1px solid black;
  font-size: 4rem;
  font-weight: 700;
  text-align: center;
  background-color: hsla(
    var(--hue),
    calc(var(--saturation) * 1%),
    calc(var(--lightness) * 1%),
    var(--alpha)
  );
}

.controls {
  width: 100%;
  max-width: 40rem;
}

.controls > div {
  margin-block-end: 2rem;
}

.reference {
  margin-inline: 0.25rem;
  margin-block-end: 1rem;
  border: 1px solid black;
  height: 1rem;
}

.controls label {
  margin-block-end: 1rem;
  display: block;
}

.controls input {
  display: block;
  width: 100%;
}

.hue .reference {
  background: linear-gradient(
    90deg,
    hsl(0, 100%, 50%) 0%,
    hsl(36, 100%, 50%) 10%,
    hsl(64, 74%, 50%) 20%,
    hsl(118, 68%, 58%) 30%,
    hsl(179, 68%, 55%) 40%,
    hsl(188, 76%, 54%) 50%,
    hsl(212, 86%, 52%) 60%,
    hsl(260, 89%, 52%) 70%,
    hsl(284, 94%, 51%) 80%,
    hsl(308, 97%, 51%) 90%,
    hsl(0, 100%, 50%) 100%
  );
}

.saturation .reference {
  background: linear-gradient(
    90deg,
    hsl(var(--hue), 0%, 50%) 0%,
    hsl(var(--hue), 100%, 50%) 100%
  );
}

.lightness .reference {
  background: linear-gradient(
    90deg,
    hsl(var(--hue), 100%, 0%) 0%,
    hsl(var(--hue), 100%, 100%) 100%
  );
}

.alpha .reference {
  background: linear-gradient(
    90deg,
    hsla(var(--hue), 100%, 50%, 0) 0%,
    hsla(var(--hue), 100%, 50%, 1) 100%
  );
}
const hue = document.querySelector("#hue");
const saturation = document.querySelector("#saturation");
const lightness = document.querySelector("#lightness");
const alpha = document.querySelector("#alpha");
const colorSwatch = document.querySelector(".color-swatch");

const rootRules = document.styleSheets[0].cssRules[0];

const getPropertyValue=(name)=>{
  return rootRules.style.getPropertyValue(`--${name}`);
}

const updateText = (prop,value)=>{
  let hue,saturation,lightness,alpha;
  if(prop === "hue"){
    hue = value;
    saturation= getPropertyValue("saturation")
    lightness= getPropertyValue("lightness");
    alpha = getPropertyValue("alpha");
  }
  if(prop === "saturation") {
    saturation = value;
    hue= getPropertyValue("hue");
    lightness= getPropertyValue("lightness");
    alpha = getPropertyValue("alpha");
  }
  if(prop === "lightness") {
    lightness = value;
    hue = getPropertyValue("hue");
    saturation= getPropertyValue("saturation")
    alpha = getPropertyValue("alpha");
  }
  if(prop === "alpha") {
    alpha = value;
    hue = getPropertyValue("hue");
    saturation= getPropertyValue("saturation")
    lightness = getPropertyValue("lightness");
  }

  colorSwatch.textContent = `hsla(${hue}, ${saturation}, ${lightness}, ${alpha})`
}

hue.addEventListener('change',(e)=>{
  let value = e.currentTarget.value;
  rootRules.style.setProperty("--hue",value);
  updateText("hue",value);
})

saturation.addEventListener('change',(e)=>{
  const value = e.target.value;
  rootRules.style.setProperty("--saturation",value);
  updateText("saturation",value);

})

lightness.addEventListener('change',(e)=>{
  const value = e.target.value;
  rootRules.style.setProperty("--lightness",value);
  updateText("lightness",value)
})

alpha.addEventListener("change",(e)=>{
  const value = parseInt(e.target.value)/100;
  rootRules.style.setProperty("--alpha",value);
  updateText("alpha",value)
})

This article explores the various ways to access, modify, and manipulate CSS styles programmatically using JavaScript. Topics covered include accessing stylesheets, finding a specific stylesheet, disabling stylesheets, inserting and deleting rules, working with computed styles, inline styles, and custom properties. The article also provides an example of building a color picker using CSS variables and JS.

ย