How to exact set a dynamic style transform using Javascript?

46,866

Solution 1

Note: you should use skewX and/or skewY instead of skew. See the MDN info here.

It would be easier to keep track of the transforms in a variable:

var elRot,    // the rotation 'counter' for the element 'el'
    elScale,  // the scale 'counter' for the element 'el'
    elSkewX,  // the skewX 'counter' for the element 'el'
    elSkewY;  // the skewY 'counter' for the element 'el'

// initialize values
elRot = 0;
elScale = 1;
elSkewX = 0;
elSkewY = 0;

or an object:

var elTranform = {
    rot: 0,   // the rotation 'counter' for the element 'el'
    sca: 1,   // the scale 'counter' for the element 'el'
    skx: 0,   // the skewX 'counter' for the element 'el'
    sky: 0    // the skewY 'counter' for the element 'el'
};

So now you can start with the function (you will still use var el for the element), the first step is getting the values, so you change the arguments given to the function:

// use this with separate vars
function setTransform (element, rotationArg, scaleArg, skewXArg, skewYArg) {
    // how to write some code to set the el transform style...
}

// use this with the object
function setTransform (element, elTransformArg) {
    // how to write some code to set the el transform style...
}

Next you have re-specify any other transforms that are 'staying', in the example you give, the skewX remains 45deg:

function setTransform (element, rotationArg, scaleArg, skewXArg, skewYArg) {
    // the brackets cause the string to be evaluated before being assigned
    element.style.transform = ("rotate() scale() skewX() skewY()");
}

or

function setTransform (element, elTransformArg) {
    element.style.transform = ("rotate() scale() skewX() skewY()");
}

Now you have to put the arguments into the string:

function setTransform (element, rotationArg, scaleArg, skewXArg, skewYArg) {
    element.style.transform = ("rotate(" + rotationArg + "deg ) scale(" + scaleArg
        + ") skewX(" + skewXArg + "deg ) skewY(" + skewYArg + "deg )");
}

or

function setTransform (element, elTransformArg) {
    element.style.transform = ("rotate(" + elTransformArg.rot + "deg ) scale("
        + elTransformArg.sca + ") skewX(" + elTransformArg.skx + "deg ) skewY("
        + elTransformArg.sky + "deg )");
}

A bit messy, so put that string in a variable (this will be beneficial for the prefixes):

function setTransform (element, rotationArg, scaleArg, skewXArg, skewYArg) {
    var transformString = ("rotate(" + rotationArg + "deg ) scale(" + scaleArg
        + ") skewX(" + skewXArg + "deg ) skewY(" + skewYArg + "deg )");
    
    // now attach that variable to each prefixed style
    element.style.webkitTransform = transformString;
    element.style.MozTransform = transformString;
    element.style.msTransform = transformString;
    element.style.OTransform = transformString;
    element.style.transform = transformString;
}

or

function setTransform (element, elTransformArg) {
    var transformString= ("rotate(" + elTransformArg.rot + "deg ) scale("
        + elTransformArg.sca + ") skewX(" + elTransformArg.skx + "deg ) skewY("
        + elTransformArg.sky + "deg )");
    
    // now attach that variable to each prefixed style
    element.style.webkitTransform = transformString;
    element.style.MozTransform = transformString;
    element.style.msTransform = transformString;
    element.style.OTransform = transformString;
    element.style.transform = transformString;
}

Now call the function:

setTransform(el, elRot, elScale, elSkewX, elSkewY);

or

setTransform(el, elTransform);

To change values, change the variables or object values and call:

elRot = 45;
elSkewX = 30;
setTransform(el, elRot, elScale, elSkewX, elSkewY);

or

elTransform.rot = 45;
elTransform.skx = 30;
setTransform(el, elTransform);

This way you only have to change the 'counter' values for each transform, and call the function to apply the transform. As mentioned above, it is important to keep any transforms that are not changed as this simply replaces the previous CSS value for the transform with the new one. Giving multiple values means only the last one is used (the C part of CSS).

Solution 2

The absolute best answer here would be a real API that allows us to affect any existing value of computed style without having to parse values. Such a thing exists in some abandonned and non-cross-browser form with the WebkitCSSMatrix API and the W3C "official" equivalent DOMMatrix. I've seen talks of reformatting and consolidating those two APIs, but they don't seem to agree on implementation. Here's a shim to make it work cross-browser : CSSMatrix class from github user arian.

How this works is that you can pass it the computed transform string (ex.: "translsate3d(0px, 0px, 100px) scale(1.2) skew(2)") and it returns an object that you can manipulate with rotate, transform, translate, skew functions. Every functions return an existing instance of itself with the newly manipulated value and you can simply re-apply its "toString" value to the element style property.

A lot of words, but here's an example (Chrome and Safari only because of the WebkitCSSMatrix usage) :

var computedStyle = window.getComputedStyle(document.querySelector('.btn'));
var matrix = new WebKitCSSMatrix(computedStyle.transform);

// Every second, we add +6 degrees of rotation, no need to read the initial value since WebkitCSSMatrix has parsed it for us
window.setInterval(function(){
  matrix = matrix.rotate(6);
  // matrix = matrix.translate(2, 0, 0);
  // matrix = matrix.scale(1.8);
  document.querySelector('.btn').style.transform = matrix.toString();
}, 1000);
body{
			padding-top: 100px;
			text-align: center;
		}

		.btn{
			text-transform: uppercase;
			border-radius:200%;
			border:1px solid #000;
			width: 2em;
			height: 2em;
			display: inline-block;
			text-align: center;
			line-height: 2em;
			text-decoration: none;
			font-family: sans-serif;
			color: #000;
      
      // It is already rotated and scaled
			transition: transform 1000ms cubic-bezier(.28,.73,.31,1);
      transform: rotate(45deg);
      -webkit-transform: rotate(45deg);
		}

		.btn:hover{
			background:#000;
			color:#fff;
      
      transform: rotate(0deg) scale(1.5);
		}
<a class="btn" href="#">|</a>
Share:
46,866
Tychio
Author by

Tychio

I'm a software engineer and UI designer. And I'm working on remote

Updated on August 11, 2022

Comments

  • Tychio
    Tychio almost 2 years

    I know method of setting transform, but I want to set the rotate and to keep others such as scale and skew.

    Perhaps, we can write a function as:

    // A variable of element, not parameter. separate out it make more clear.
    var el = document.getElementById('el');
    function setTransform (p_name, p_value) {
      // how to write some code to set the el transform style...
    }
    

    Moreover, I have a element:

    <div id="el" style="transform: rotate(45deg) skew(45deg);"></div>
    

    And to us our function setTransform:

    setTransform('rotate', '30deg');
    

    I hope the element is this:

    <div id="el" style="transform: rotate(30deg) skew(45deg);"></div>
    

    So my question is how to write content of the function setTransform?

  • Tychio
    Tychio over 10 years
    no help. I know browsers vendor prefix. I want to only set rotate and to keep skew and others.Please careful reading my question.
  • Nikhil N
    Nikhil N over 10 years
    oh..probably you'll need to do some string manipulation around it.
  • Tychio
    Tychio over 10 years
    RegEx? It's ok if haven't other way.
  • Xander Luciano
    Xander Luciano about 7 years
    Would just like to point out that on that MDN page there is a note that says: "Gecko 14.0 removed the experimental support for skew(), but it was reintroduced in Gecko 15.0 for compatibility reasons. As it is non-standard and will likely be removed in the future, do not use it."