Tutorial CSS object model through its JavaScript API πŸš€πŸš€πŸš€

Tutorial CSS object model through its JavaScript API πŸš€πŸš€πŸš€

Tutorial CSS object model through its JavaScript API - The CSS Object Model is a set of APIs allowing the manipulation of CSS from JavaScript. It is much like the DOM, but for the CSS rather than the HTML. It allows users to read and modify CSS style dynamically...🌟🌟🌟🌟🌟

Tutorial CSS object model through its JavaScript API - The CSS Object Model is a set of APIs allowing the manipulation of CSS from JavaScript. It is much like the DOM, but for the CSS rather than the HTML. It allows users to read and modify CSS style dynamically...

Beyond all preprocessors, transpiler, or whatever web development tool you can think of, one fact still remains true - it's HTML, CSS, and JavaScript that power today's web. Any kind of tool, language and etc., all that remains dependent on these 3 technologies (if we don't count the uprising WebAssembly). They work and interact together, to provide you with limitless possibilities to create newer, better and even more stunning things!

JavaScript is - if we can call it that way - the king of interactivity. Its capabilities as a scripting language itself, combined with numerous web APIs extending its feature-set even further, are truly impressive. Examples of such APIs include the most well-known WebGL API, Canvas API, DOM API, but also a lesser-known set of CSS-related methods, which can be called (unofficially) CSS API. And that's what we're going to explore in today's post!

While the idea of interacting with DOM through its JS API was made really popular thanks to concepts such as JSX and countless JS frameworks, the use of similar techniques with CSS doesn't seem to have that much attention. Of course, CSS-in-JS solutions exist, but the most popular ones are rather based on transpilation, outputting CSS without any additional runtime in production. That's good for performance for sure, as CSS API usage may cause additional reflows, which makes it just as demanding as the use of DOM API. But still, this isn't what we're looking for. What if I tell you that you can not only manipulate DOM elements' styles and CSS classes but also create full-blown stylesheets, just like with HTML, just with the use of JS?

Basics Inline styles

Before we'll dive deep into the complex stuff, let's first remind ourselves some basics. Like the fact that you can edit the given HTMLElement's inline styles by modifying its .style property.

const el = document.createElement("div");

el.style.backgroundColor = "red";
// or
el.style.cssText = "background-color: red";
// or
el.setAttribute("style", "background-color: red");

Setting your style properties directly on the .style object will require you to use camelCase as your property keys, instead of kebab-case. If you have much more inline style properties to set (although, in such case, you may consider using CSS classes), you can do this in a bit more performant way, by setting the .style.cssText property or by setting the style attribute. Keep in mind that this will completely reset the inline styles of your element, and thus, requires you to include all properties (even the unchanged ones) in the string argument. If such micro-optimizations don't interest you (they really shouldn't), and your targeting modern browsers, you can consider using .style with Object.assign(), to set multiple style properties at once.

// ...
Object.assign(el.style, {
    backgroundColor: "red",
    margin: "25px"
});

There's a bit more to these "basics" than you'd probably think of. The .styleobject implements the CSSStyleDeclaration interface. This means that it comes with some interesting properties and methods! This includes known to us .cssText, but also .length (number of set properties), and methods like .item(), .getPropertyValue() and .setPropertyValue(), allowing you to operate on inline styles, without the use of camelCase, and thus - any case conversion. You can find the complete API documented on MDN.

// ...
const propertiesCount = el.style.length;
for(let i = 0; i < propertiesCount; i++) {
    const name = el.style.item(i); // e.g. "background-color"
    const value = el.style.getPropertyValue(name); // e.g. "red"
    const priority = el.style.getPropertyPriority(name); // e.g. "important"

    if(priority === "important") {
        el.style.removeProperty();
    }
}

Just a small tidbit - the .item() method that's most useful during iterations, has the alternate syntax in the form of access by index.

// ...
el.style.item(0) === el.style[0]; // true

CSS classes

Now, let's leave inline styles for a moment and take a look at higher structures - CSS classes. The basics include the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/className" target="_blank">.className</a> which has a form of a string when retrieved and set.

// ...
el.className = "class-one class-two";
el.setAttribute("class", "class-one class-two");

Another way of setting classes string is by setting the class attribute (same for retrieval). But, just like with .style.cssText property, setting.className would require you to include all classes of the given element in the string, including the changed and unchanged ones. Of course, some simple string operations can do the job, but surely there has to be another way... And there is! It's provided to us in the form of slightly-newer.classList property. By "slightly newer" I mean that it isn't supported by IE 9, and only partially supported by IE 10 and IE 11.

The .classList property implements <a href="https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList" target="_blank">DOMTokenList</a>, giving you access to a whole bunch of useful methods. Likes of .add(), .remove(), .toggle() and.replace() allow you to change the current set of CSS classes, while others, e.g. .item(), .entries() or .forEach() simplify the iteration process of this index collection.

// ...
const classNames = ["class-one", "class-two", "class-three"];
classNames.forEach(className => {
    if(!el.classList.contains(className)) {
        el.classList.add(className);
    }
});

Stylesheets

Now that we're done with the revision, let's start creating our JS-only stylesheet! First, let's break down all the details behind what's going on.

Going from top to the bottom, we have the <a href="https://developer.mozilla.org/en-US/docs/Web/API/StyleSheetList" target="_blank">StyleSheetList</a> interface, implemented by document.styleSheets property. It helps to represent the situation seen in standard HTML code - the use of multiple stylesheets in one document. Whether it's from an external file, URL or within <style/>tag, document.styleSheets collects them all in an indexed collection, implementing standard iteration protocols. With that said, you can access all the <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet" target="_blank">CSSStyleSheet</a>s with a simple loop.

for(styleSheet of document.styleSheets){
    console.log(styleSheet);
}

As that's all there is to StyleSheetList, let's go over to CSSStyleSheet itself. It's here where things start to get interesting! CSSStyleSheet extends<a href="https://developer.mozilla.org/en-US/docs/Web/API/StyleSheet" target="_blank">StyleSheet</a> interface, and, with this relation come only a few read-onlyproperties, like .ownerNode, .href, .title or .type, that are mostly taken straight from the place where given stylesheet was declared. Just recall the standard HTML code for loading external CSS file, and you'll know what I'm talking about.

<head>
<link rel="stylesheet" type="text/css" href="style.css" title="Styles">
</head>

So, all the stuff that interests us the most is inside the CSSStyleSheetinterface. Now, we know that HTML document can contain multiple stylesheets, and now... all these stylesheets can contain different rules or even more stylesheets (when using @import) within them! And that's the point we're at. CSSStyleSheet gives you access to two methods -.insertRule() and .deleteRule().

// ...
const ruleIndex = styleSheet.insertRule("div {background-color: red}");
styleSheet.deleteRule(ruleIndex);

These methods operate with indices and CSS-like strings. As CSS rules order is important to decide which one should be used in case of conflict,.insertRule() allows you to pass an optional index for your new rule. Know that some misuses may result in an error, so... just keep it simple.

CSSStyleSheet also has two properties of its own - .ownerRule and.cssRules. While .ownerRule is related to @import stuff, it's the second one - the .cssRules - that interests us the most. Simply put, it's a <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSRuleList" target="_blank">CSSRuleList</a> of<a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSRule" target="_blank">CSSRule</a>s, that can be modified with earlier-mentioned .insertRule()and.deleteRule() methods. Keep in mind, that some browsers may block you from accessing .cssRules property of external CSSStyleSheet from a different origin (domain).

So, what about CSSRuleList? Again, it's an iterable collection of CSSRules, meaning that you can iterate over it, access its CSSRules by their indices or.item() method. What you cannot do though is modifying CSSRuleListdirectly. It can only be done with previously mentioned methods and nothing else.

The CSSRuleList contains object implementing CSSRule interface. This one comes with properties such as .parentStyleSheet and - most importantly -.cssText, containing all the CSS code of the given rule. There's yet one more interesting property - .type. It indicates the type of given CSSRule, according to specified constants. You should remember, that besides the most often use "standard" style-related rules, CSS can consist of e.g. @importor @keyframes (most notably) rules. CSSRules of different types have corresponding interfaces. As you won't be creating them directly, but rather with CSS-like strings, you don't really have to know anything more, that the properties that these extended interfaces provide.

In case of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleRule" target="_blank">CSSStyleRule</a>, these properties are .selectorText and .style. First one indicates the selector used for the rule in the form of a string, and the second one is an object implementing CSSStyleDeclaration interface, which we've discussed before.

// ...
const ruleIndex = styleSheet.insertRule("div {background-color: red}");
const rule = styleSheet.cssRules.item(ruleIndex);

rule.selectorText; // "div"
rule.style.backgroundColor; // "red"

Implementation

At this point, I think we know enough about CSS-related JavaScript APIs, to create our own, tiny, runtime-based CSS-in-JS implementation. The idea is that we'll create a function, that passed a simple style configuration object, will output a hashed name of newly created CSS class for later use.

So, our workflow here is pretty simple. We need a function that has access to some sort of stylesheet and just use .insertRule() method together with phrased style config to make everything ticking. Let's start with the stylesheet part.

function createClassName(style) {
  // ...
  let styleSheet;
  for (let i = 0; i < document.styleSheets.length; i++) {
    if (document.styleSheets[i].CSSInJS) {
      styleSheet = document.styleSheets[i];
      break;
    }
  }
  if (!styleSheet) {
    const style = document.createElement("style");
    document.head.appendChild(style);
    styleSheet = style.sheet;
    styleSheet.CSSInJS = true;
  }
  // ...
}

If you're using ESM or any other kind of JS module system, you can safely create your stylesheet instance outside the function and not worry about other people accessing it. But, as I wanted to keep this example minimal, we'll just set the .CSSInJS property on our stylesheet as a form of a flag, informing us if this is the one we want to use.

That's pretty much all about the first part of the code snippet above. Now, what if we have to create a new stylesheet for our purposes? There's no straight-forward way of doing this. Our best bet would be to create a new<style/> tag and append it to our HTML document's <head/>section. This automatically adds a new stylesheet to the document.styleSheets list and allows us to access it by the .sheet property of our <style/> tag. Pretty clever, huh?

function createRandomName() {
  const code = Math.random().toString(36).substring(7);
  return `css-${code}`;
}

function phraseStyle(style) {
  const keys = Object.keys(style);
  const keyValue = keys.map(key => {
    const kebabCaseKey = 
        key.replace(/([a-z])([A-Z])/g, "function createRandomName() {
  const code = Math.random().toString(36).substring(7);
  return `css-${code}`;
}

function phraseStyle(style) {
  const keys = Object.keys(style);
  const keyValue = keys.map(key => {
    const kebabCaseKey = 
        key.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
    const value = 
        `${style[key]}${typeof style[key] === "number" ? "px" : ""}`;

    return `${kebabCaseKey}:${value};`;
  });

  return `{${keyValue.join("")}}`;
}
-$2").toLowerCase();
    const value = 
        `${style[key]}${typeof style[key] === "number" ? "px" : ""}`;

    return `${kebabCaseKey}:${value};`;
  });

  return `{${keyValue.join("")}}`;
}

Actually, beyond the tiny tidbit above, there's really no more similarly-interesting stuff going on. Naturally, we first need a way to generate a new,random name for our CSS class. Then, we need to properly phrase our style object, to the form of viable CSS string (or at least part of it). This includes the conversion between camelCase and kebab-case, and, optionally, handling of pixel unit(px) conversion. Oh, and don't forget the semicolon (;) at the end of every key-value pair!

function createClassName(style) {
  const className = createRandomName();
  let styleSheet;
  // ...
  styleSheet.insertRule(`.${className}${phraseStyle(style)}`);
  return className;
}

Then, we go to our main function and make the required adjustments. We generate the random name and insert the CSS rule to the stylesheet. As all out rules are about classes, they all require a dot on their respective beginning for a proper selector. Believe me, it's super easy to forget!

const redRect = createClassName({
  width: 100,
  height: 100,
  backgroundColor: "red"
});

el.classList.add(redRect);

With all set and done, we can finally put our code to the final test! Everything should work just fine! Below is a CodePen to prove that.

What do you think?

As you can see, manipulating CSS from JavaScript level is very interesting. Whether you know it's possible or not, you must admit - it's pretty awesome. Our little example above is only a proof-of-concept. There's a lot more potential within CSS API (or rather APIs). And it's just waiting to be unveiled!

Teaching CSS to JavaScripters

Teaching CSS to JavaScripters

Teaching CSS to JavaScripters. Some JavaScript professionals do not know CSS as well as they’d like to. In order to help them, PPK decided to write a book β€œCDD for JavaScripters”, where he will explain CSS in terms that JavaScripters will understand. But what are those terms? What kind of teaching would JavaScripters expect? Is there a CSS mental model that is different from a JavaScript mental model? Is CSS a programming language? Does that matter for teaching or learning CSS?

But what are those terms? What kind of teaching would JavaScripters expect? Is there a CSS mental model that is different from a JavaScript mental model? Is CSS a programming language? Does that matter for teaching or learning CSS?

PPK will give a brief outline of where he’s standing right now on these questions. The majority of the session will be used for a discussion with the audience. How should he teach CSS? Why is it so hard for some to understand it? What are the least-understood parts of CSS? We hope this discussion will yield valuable feedback.

Traffic Lights Simulator with CSS and JavaScript

Traffic Lights Simulator with CSS and JavaScript

Traffic Lights Simulator with CSS and JavaScript. In this tutorial we're going to build a Traffic Light simulator using CSS and JavaScript.

In this tutorial we're going to build a Traffic Light simulator using CSS and JavaScript.

How to Create an Animated Navbar with Html, CSS and JavaScript

How to Create an Animated Navbar with Html, CSS and JavaScript

In this Html, CSS and JavaScript tutorial you will build an animated navbar with Html, CSS and JavaScript. Have you every wanted to create an awesome animated navbar with JavaScript? We will be looking at making a navbar with Html and CSS and then using the intersection observer in JavaScript to help us create this effect.

Javascript Animated Navbar Tutorial

Have you every wanted to create an awesome animated navbar with javascript? no? oh well...this is akward.

All jokes aside we will be looking at making a navbar with html and css and then using the intersection observer in javascript to help us create this effect.