Scripting CSS transforms with DOMMatrix

ยท

4 min read

I recently discovered that the css transform of an element can be set to the stringified value of a DOMMatrix. This means that we can use a DOMMatrix instance to construct the transforms, scales, and rotatess that we need, and then set that instance as the value of the element's transform. In Code This Means we can do this:

function updateTransform(element) {
  element.style.transform = new DOMMatrix([val1, val2, val3, val4, val5, val6]);
}

You may be wondering how this matches up to the css transform functions that you already know, which involve syntax like transform: translate(10px, 50px) scale(0.9). The answer is that the familiar syntax is represented by a special matrix under the hood, so using DOMMatrix is our way of accessing and manipulating that matrix directly.

A simple example is that for an element with those same transforms I used above (translate(10px, 50px) scale(0.9)), the corresponding DOMMatrix looks like this:

{
    "a": 0.9,
    "b": 0,
    "c": 0,
    "d": 0.9,
    "e": 10,
    "f": 50,
    "m11": 0.9,
    "m12": 0,
    "m13": 0,
    "m14": 0,
    "m21": 0,
    "m22": 0.9,
    "m23": 0,
    "m24": 0,
    "m31": 0,
    "m32": 0,
    "m33": 1,
    "m34": 0,
    "m41": 10,
    "m42": 50,
    "m43": 0,
    "m44": 1,
    "is2D": true,
    "isIdentity": false
}

... ๐Ÿ‘€ that's complicated!

Despite how confusing that may look, there are 2 main takeaways I want you to have:

  • there are 2 types of matrices, 2d and 3d. 2d requires 6 values to form the matrix, whereas 3d requires 16 values. 2d can represent simpler transforms (like my example above) and is represented by the properties from a-f in the object shown above, whereas 3d can represent more complex transforms and is represented by the properties from m11 to m44.
  • you can directly change these values to update the transforms applied to your DOM nodes! This was the light bulb ๐Ÿ’ก moment for me. I was trying to update the transformX and transformY of an element without using ugly regexes like /translate\(\d+,\d+\)/ (yuck! ๐Ÿคฎ) because the element had its transform set by an external stylesheet.

My solution built on this second takeaway, particularly because you can construct a DOMMatrix using the existing transform of an element. In Code This Means:

function updateScaleUsingMatrix(element) {
  const currentTransformMatrix = new DOMMatrix(getComputedStyle(element).transform);
  // I can bump up the scale by just incrementing the numbers!
  currentTransformMatrix.a += 0.3;
  currentTransformMatrix.d += 0.3;
  element.style.transform = currentTransformMatrix;
  // or more explicitly,
  // element.style.transform = currentTransformMatrix.toString();
}

Additional Notes

  • Incrementing numerical css values directly is part of the promise of the Typed OM API that's part of CSS Houdini. Unfortunately it's not yet fully implemented by browsers, most notably in Safari and Firefox. So you could view this matrix method as a fallback for those two browsers if you're already using Typed OM in the chromium-based browsers that support it.
  • You could claim, fairly in my opinion, that this is not very intuitive. There's still the challenge of knowing which of those properties from a-f and m11-m44 represent the normal css transform functions that we're used to. For simpler transforms involving scale() and translate(), MDN has an example showing how scale() and transform() map to the matrix properties. There's also this package that will give you the familiar syntax of transform functions, so you can use the DOMMatrix as your intermediate only for manipulating the numbers. You can then convert back to the familiar transform functions to preserve readability. To be clear I have no affiliation with that package, I only came across it during my research. Most importantly, my recommendation is that you should test and see what properties of the 2d/3d matrix change when you perform your desired update, and then write your logic to update only that property.
  • DOMMatrix has the alias WebKitCSSMatrix in some browsers. In Code This Means const getMatrix = window.DOMMatrix || window.WebKitCSSMatrix; can cover both possibilities.

So, I hope you found this as fascinating as I did. I punched the air and let out a loud "yussssss!" (yes I'm that type of dev ๐Ÿ˜‚) when I realized this could work for me. Feel free to reach out to share your enthusiasm, ask any questions, correct errors, or just say hi to me @cinexa7254 on Twitter. Thanks for your time!

TL;DR

  • You can use DOMMatrix to represent your css transforms and easily manipulate the numbers like any other javascript variable.
  • You can map the familiar syntax of css transform functions to individual properties in the matrix either by testing manually to see what matrix properties change as you update the transform, or by using a package that does this for you, such as this one (I'm NOT affiliated with that project, I only came across it as part of my research).
ย