The CSS Typed Object Model API exposes CSS values as typed JavaScript objects to allow their performant manipulation.
Converting CSS Object Model value strings into meaningfully-typed JavaScript representations and back (via HTMLElement.style) can incur a significant performance overhead.
The CSS Typed OM makes CSS manipulation more logical and performant by providing object features (rather than CSSOM string manipulation), providing access to types, methods, and an object model for CSS values.
This article provides an introduction to all of its main features.
computedStyleMap()
With the CSS Typed OM API, we can access all the CSS properties and values — including custom properties — that are impacting an element. Let's see how this works by creating our first example, which explores computedStyleMap().
Getting all the properties and values
HTML
We start with some HTML: a paragraph with a link, as well as a definition list to which we will add all the CSS Property / Value pairs.
We add JavaScript to grab our unstyled link and return back a definition list of all the default CSS property values impacting the link using computedStyleMap().
js
// Get the elementconst myElement = document.querySelector("a");// Get the <dl> we'll be populatingconst stylesList = document.querySelector("#regurgitation");// Retrieve all computed styles with computedStyleMap()const defaultComputedStyles = myElement.computedStyleMap();// Iterate through the map of all the properties and values, adding a <dt> and <dd> for eachfor(const[prop, val]of defaultComputedStyles){// propertiesconst cssProperty = document.createElement("dt");
cssProperty.appendChild(document.createTextNode(prop));
stylesList.appendChild(cssProperty);// valuesconst cssValue = document.createElement("dd");
cssValue.appendChild(document.createTextNode(val));
stylesList.appendChild(cssValue);}
The computedStyleMap() method returns a StylePropertyMapReadOnly object containing the size property, which indicates how many properties are in the map. We iterate through the style map, creating a <dt> and <dd> for each property and value respectively.
Did you realize how many default CSS properties a link had? Update the JavaScript on line 2 to select the <p> rather than the <a>. You'll notice a difference in the margin-top and margin-bottom default computed values.
.get() method / custom properties
Let's update our example to only retrieve a few properties and values. Let's start by adding some CSS to our example, including a custom property and an inheritable property:
Instead of getting all the properties, we create an array of properties of interest and use the StylePropertyMapReadOnly.get() method to get each of their values:
js
// Get the elementconst myElement = document.querySelector("a");// Get the <dl> we'll be populatingconst stylesList = document.querySelector("#regurgitation");// Retrieve all computed styles with computedStyleMap()const allComputedStyles = myElement.computedStyleMap();// Array of properties we're interested inconst ofInterest =["font-weight","border-left-color","color","--color"];// iterate through our properties of interestfor(const value of ofInterest){// Propertiesconst cssProperty = document.createElement("dt");
cssProperty.appendChild(document.createTextNode(value));
stylesList.appendChild(cssProperty);// Valuesconst cssValue = document.createElement("dd");
cssValue.appendChild(document.createTextNode(allComputedStyles.get(value)));
stylesList.appendChild(cssValue);}
We included border-left-color to demonstrate that, had we included all the properties, every value that defaults to currentcolor (including caret-color, outline-color, text-decoration-color, column-rule-color, etc.) would return rgb(255, 0, 0). The link has inherited font-weight: bold; from the paragraph's styles, listing it as font-weight: 700. Custom properties, like our --color: red, are properties. As such, they are accessible via get().
You'll note that custom properties retain the value as written in the stylesheet, whereas computed styles will be listed as the computed value — color was listed as an rgb() value and the font-weight returned was 700 even though we use a named color and the bold keyword.
CSSUnitValue and CSSKeywordValue
The power of the CSS Typed OM is that values are separate from units; parsing and concatenating string values may become be a thing of the past. Every CSS property in a style map has a value. If the value is a keyword, the object returned is a CSSKeywordValue. If the value is numeric, a CSSUnitValue is returned.
CSSKeywordValue is a class that defines keywords like inherit, initial, unset, and other strings you don't quote, such as auto and grid. This subclass gives you a value property via cssKeywordValue.value.
CSSUnitValue is returned if the value is a unit type. It is a class that defines numbers with units of measurement like 20px, 40%, 200ms, or 7. It is returned with two properties: a value and a unit. With this type we can access the numeric value — cssUnitValue.value — and its unit — cssUnitValue.unit.
Let's write a plain paragraph, apply no styles, and inspect a few of its CSS properties by returning a table with the unit and value:
html
<p>
This is a paragraph with some content. Open up this example in Codepen or
JSFiddle, and change some features. Try adding some CSS, such as a width
for this paragraph, or adding a CSS property to the ofInterest array.
</p><tableid="regurgitation"><thead><tr><th>Property</th><th>Value</th><th>Unit</th></tr></table>
For each property of interest, we list the name of the property, use .get(propertyName).value to return the value, and, if the object returned by the get() is a CSSUnitValue, list the unit type we retrieve with .get(propertyName).unit.
js
// Get the element we're inspectingconst myElement = document.querySelector("p");// Get the table we'll be populatingconst stylesTable = document.querySelector("#regurgitation");// Retrieve all computed styles with computedStyleMap()const allComputedStyles = myElement.computedStyleMap();// Array of properties we're interested inconst ofInterest =["padding-top","margin-bottom","font-size","font-stretch","animation-duration","animation-iteration-count","width","height",];// Iterate through our properties of interestfor(const value of ofInterest){// Create a rowconst row = document.createElement("tr");// Add the name of the propertyconst cssProperty = document.createElement("td");
cssProperty.appendChild(document.createTextNode(value));
row.appendChild(cssProperty);// Add the unitless valueconst cssValue = document.createElement("td");// Shrink long floats to 1 decimal pointlet propVal = allComputedStyles.get(value).value;
propVal = propVal %1? propVal.toFixed(1): propVal;
cssValue.appendChild(document.createTextNode(propVal));
row.appendChild(cssValue);// Add the type of unitconst cssUnit = document.createElement("td");
cssUnit.appendChild(
document.createTextNode(allComputedStyles.get(value).unit),);
row.appendChild(cssUnit);// Add the row to the table
stylesTable.appendChild(row);}
For those of you using a non-supporting browser, the above output should look something like this:
You'll note the <length> unit returned is px, the <percentage> unit returned is percent, the <time> unit is s for 'seconds', and the unitless <number> unit is number.
We didn't declare a width or a height for the paragraph, both of which default to auto and therefore return a CSSKeywordValue instead of a CSSUnitValue. CSSKeywordValues do not have a unit property, so in these cases our get().unit returns undefined.
Had the width or height been defined in a <length> or <percent>, the CSSUnitValue unit would have been px or percent respectively.
Let's examine a CSS example with several custom properties, transforms, calc()s, and other features. We'll take a look at what their types are by employing short JavaScript snippets outputting to console.log():
When we invoke get(), a custom property of type CSSUnparsedValue is returned. Note the space before the 1.2rem. To get a unit and value, we need a CSSUnitValue, which we can retrieve using the CSSStyleValue.parse() method on the CSSUnparsedValue.
Although the <button> element is an inline element by default, we've added display: inline-block; to enable sizing. In our CSS we have width: calc(30% + 20px);, which is a calc() function to define the width.
When we get() the transform property, we get a CSSTransformValue. We can query the length (or number) of transform functions with the length property.
Seen as we have a length of 1, which represents a single transform function, we log the first object and get a CSSScale object. We get CSSUnitValues when we query the x, y, and z scaling. The readonly CSSScale.is2D property is true in this scenario.
Had we added translate(), skew(), and rotate() transform functions, the length would have been 4, each with their own x, y, z values, and each with an .is2D property. For example, had we included transform: translate3d(1px, 1px, 3px), the .get('transform') would have returned a CSSTranslate with CSSUnitValues for x, y, and z, and the readonly .is2D property would have been false.
CSSImageValue
Our button has one background image: a magic wand.
When we get() the 'background-image', a CSSImageValue is returned. While we used the CSS background shorthand property, the inherited Object.prototype.toString() method, shows we returned only the image, 'url("magicwand.png")'.
Notice that the value returned is the absolute path to the image — this is returned even if the original url() value was relative. Had the background image been a gradient or multiple background images, .get('background-image') would have returned a CSSStyleValue. The CSSImageValue is returned only if there is a single image, and only if that single image declaration is a URL.
Summary
This should get you started with understanding the CSS Typed OM. Take a look at all the CSS Typed OM interfaces to learn more.