Using the CSS Typed Object Model
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.
<p> <a href="https://example.com">Link</a> </p> <dl id="regurgitation"></dl>
JavaScript
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()
.
// get the element const myElement = document.querySelector('a'); // get the <dl> we'll be populating const stylesList = document.querySelector('#regurgitation'); // Retrieve all computed styles with computedStyleMap() const defaultComputedStyles = myElement.computedStyleMap(); // iterate thru the map of all the properties and values, adding a <dt> and <dd> for each for (const [prop, val] of defaultComputedStyles) { // properties const cssProperty = document.createElement('dt'); cssProperty.appendChild(document.createTextNode(prop)); stylesList.appendChild(cssProperty); // values const 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.
Result
In browsers that support computedStyleMap()
, you'll see a list of all the CSS properties and values. In other browsers, you'll just see a link.
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:
p { font-weight: bold; } a { --color: red; color: var(--color); }
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:
// get the element const myElement = document.querySelector('a'); // get the <dl> we'll be populating const stylesList = document.querySelector('#regurgitation'); // Retrieve all computed styles with computedStyleMap() const allComputedStyles = myElement.computedStyleMap(); // array of properties we're interested in const ofInterest = ['font-weight', 'border-left-color', 'color', '--color']; // iterate through our properties of interest for ( let i = 0; i < ofInterest.length; i++ ) { // properties const cssProperty = document.createElement('dt'); cssProperty.appendChild(document.createTextNode(ofInterest[i])); stylesList.appendChild(cssProperty); // values const cssValue = document.createElement('dd'); cssValue.appendChild(document.createTextNode( allComputedStyles.get(ofInterest[i]))); 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:
<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> <table id="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
.
// get the element we're inspecting const myElement = document.querySelector('p'); // get the table we'll be populating const stylesTable = document.querySelector('#regurgitation'); // Retrieve all computed styles with computedStyleMap() const allComputedStyles = myElement.computedStyleMap(); // array of properties we're interested in const ofInterest = ['padding-top', 'margin-bottom', 'font-size', 'font-stretch', 'animation-duration', 'animation-iteration-count', 'width', 'height']; // iterate thru our properties of interest for ( let i = 0; i < ofInterest.length; i++ ) { // create a row const row = document.createElement( 'tr' ); // add the name of the property const cssProperty = document.createElement( 'td' ); cssProperty.appendChild( document.createTextNode( ofInterest[i] ) ); row.appendChild( cssProperty ); // and the unitless value const cssValue = document.createElement( 'td' ); // shrink long floats to 1 decimal point let propVal = allComputedStyles.get( ofInterest[i] ).value; propVal = ( propVal % 1 ) ? propVal.toFixed( 1 ) : propVal; cssValue.appendChild(document.createTextNode( propVal )); row.appendChild( cssValue ); // and the type of unit const cssUnit = document.createElement( 'td' ); cssUnit.appendChild( document.createTextNode( allComputedStyles.get( ofInterest[i] ).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 looks something like this:
Property | Value | Unit |
---|---|---|
padding-top | 0 | px |
margin-bottom | 16 | px |
font-size | 16 | px |
font-stretch | 100 | % |
animation-duration | 0 | px |
animation-iteration-count | 1 | number |
width | auto | undefined |
height | auto | undefined |
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
. CSSKeywordValue
s 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.
There are other types available:
- An
<image>
will return aCSSImageValue
. - A
<color>
would return aCSSStyleValue
. - A
transform
returns aCSSTransformValue
. - A custom property returns a
CSSUnparsedValue
.
You can use a CSSUnitValue
or CSSKeywordValue
to create other objects. For example, the parameters for a CSSPositionValue
is one to two CSSUnitValues
or CSSKeywordValues
, or one of each:
let position = new CSSPositionValue( new CSSKeywordValue("center"), new CSSUnitValue(10, "px"));
CSSStyleValue
The CSSStyleValue
interface of the CSS Typed Object Model API is the base class of all CSS values accessible through the Typed OM API, including CSSImageValue
, CSSKeywordValue
, CSSNumericValue
, CSSPositionValue
, CSSTransformValue
, and CSSUnparsedValue
.
It has two methods:
As noted above, StylePropertyMapReadOnly.get('--customProperty')
returns a CSSUnparsedValue
. We can parse CSSUnparsedValue
object instances with the inherited CSSStyleValue.parse()
and CSSStyleValue.parseAll()
methods.
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()
:
:root { --mainColor: hsl(198, 43%, 42%); --black: hsl(0, 0%, 16%); --white: hsl(0,0%,97%); --unit: 1.2rem; } button { --mainColor: hsl(198, 100%, 66%); display: inline-block; padding: var(--unit) calc(var(--unit)*2); width: calc(30% + 20px); background: no-repeat 5% center url(magicwand.png) var(--mainColor); border: 4px solid var(--mainColor); border-radius: 2px; font-size: calc(var(--unit)*2); color: var(--white); cursor: pointer; transform: scale(0.95); }
Let's add the class to a button (a button which does nothing).
<button>Styled Button</button>
We grab our StylePropertyMapReadOnly
with the following JavaScript:
const allComputedStyles = document.querySelector('button').computedStyleMap();
The following examples reference allComputedStyles
:
CSSUnparsedValue
The CSSUnparsedValue
represents custom properties:
// CSSUnparsedValue let unit = allComputedStyles.get('--unit'); console.log( unit ) // CSSUnparsedValue {0: " 1.2rem", length: 1} console.log (unit[0] ) // " 1.2rem"
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
.
let parsedUnit = CSSNumericValue.parse( unit ); console.log( parsedUnit ); // CSSUnitValue {value: 1.2, unit: "rem"} console.log( parsedUnit.unit ); // "rem" console.log( parsedUnit.value ); // 1.2
CSSMathSum
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 width
, we get a CSSMathSum
returned. CSSMathSum.values
is a CSSNumericArray
with 2 CSSUnitValues
.
The value of CSSMathValue.operator
is sum
:
let btnWidth = allComputedStyles.get('width') console.log( btnWidth ); // CSSMathSum console.log( btnWidth.values ); // CSSNumericArray {0: CSSUnitValue, 1: CSSUnitValue, length: 2} console.log( btnWidth.operator ); // 'sum'
CSSTransformValue with CSSScale
The display: inline-block;
also enables transforming. In our CSS we have transform: scale(0.95);
, which is a transform
function.
let transform = allComputedStyles.get('transform'); console.log( transform ); // CSSTransformValue {0: CSSScale, 1: CSSTranslate, length: 2, is2D: true} console.log( transform.length ); // 1 console.log( transform[0] ); // CSSScale {x: CSSUnitValue, y: CSSUnitValue, z: CSSUnitValue, is2D: true} console.log( transform[0].x ); // CSSUnitValue {value: 0.95, unit: "number"} console.log( transform[0].y ); // CSSUnitValue {value: 0.95, unit: "number"} console.log( transform[0].z ); // CSSUnitValue {value: 1, unit: "number"} console.log( transform.is2D ); // true
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.
let bgImage = allComputedStyles.get('background-image'); console.log( bgImage ); // CSSImageValue console.log( bgImage.toString() ); // url("magicwand.png")
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.
See also
© 2005–2021 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/CSS_Typed_OM_API/Guide