Skip to content

Correcting Transform Origin and Translate in IE

heygrady edited this page Sep 14, 2010 · 15 revisions

The matrix filter in IE has some severe limitations, not the least of which is that matrices are not something most people are used to calculating. While calculating a matrix can be easily done in JavaScript using the Sylvester library, IE also lacks support for transform-origin and translate(). This means it’s not an easy task to make a transformation look the same in IE as it does in Mozilla or WebKit.

Correcting Transform Origin

IE kind of locks the transform origin at 0, 0. However, IE also glues the top-most and left-most pixels of the transformed object to the top and left of the original object. This means that the top-left corner of the transformation is rarely where you’d expect it to be; you can’t simply set the transform-origin to 0, 0 in the other browsers. Correcting for this is not an easy task but the core of the solution relies on relative positioning; calculating the top and left takes some math.

  1. Remember the height and width! (IE forgets)
  2. Calculate a matrix (we’ll use a matrix for rotate(45)) and apply it to the element
  3. Ensure the element is positioned
  4. Calculate the offset between a 0, 0 origin and our desired origin (50%, 50% is the default in non-IE browsers)
  5. Calculate the new top and left of the transformed element
  6. Set top to offset.y + ty + newTop
  7. Set left to offset.x + tx + newLeft

Remember the height and width

After a matrix is applied to an element IE reports the height and width of the new element accurately which is actually wrong. In all other browsers, a 100px by 100px <div> continues to think it is that size no matter how it is transformed. IE on the other hand returns the resulting dimensions. So if a 100px by 100px <div> is rotated 45deg, IE correctly reports that the <div> is actually 141px by 141px. But this is a huge hassle if we’re trying to run the original <div>’s dimensions through a matrix to figure out how to re-position it.

There are two potential solutions to the issue:

  • Remember $('.example').height(), $('.example').width(), $('.example').outerHeight() and $('.example').outerWidth() in variables. (This is how I fixed this initially.)
  • Remove the matrix temporarily, read the required dimensions, and then re-apply the matrix. (This is how I’ll fix this the next time I work on the plug-in.)

Calculate a matrix

For this example, we’re going to rotate a <div> 45 degrees. For fun, we’ll start working on a crazy-simple and not very useful plug-in for doing our rotation. Below you’ll see that our example plug-in only supports rotate and only works in IE. It also creates a matrix using Sylvester for no good reason.

$('.example').transform({rotate: 45}); // rotates 45 degrees

//for fun, we'll write a super simple plug-in
$.fn.transform = function(funcs) {
    this.each(function() {
        var elem = this;
        $.each(funcs, function(func, val) {
            if (func == 'rotate') {
                rotate(elem, val);
            }
        });
    });
}

function rotate(elem, val) {
    var $elem = $(elem);

    if (!$.browser.msie) {
        //do it the easy way
    }

    // use parseFloat twice to kill exponential number and avoid things like 0.00000000
    var rad = deg * (Math.PI/180),
        costheta = parseFloat(parseFloat(Math.cos(rad)).toFixed(8)),
        sintheta = parseFloat(parseFloat(Math.sin(rad)).toFixed(8));

    // collect all of the values  in our matrix
    var a = costheta,
        b = sintheta,
        c = -sintheta,
        d = costheta,
        tx = 0,
        ty = 0;

    // Transform the element in IE
    elem.style.filter = 'progid:DXImageTransform.Microsoft.Matrix(M11=' + a + ', M12=' + c + ', M21=' + b + ', M22=' + d + ', sizingMethod=\'auto expand\')';

    // use the Sylvester $M() function to create a matrix
    var matrix = $M([
      [a, c, tx],
      [b, d, ty],
      [0, 0, 1]
    ]);
}

Calculate the origin offset

We don’t want to be stuck with IE’s sort-of-top-left origin, we want to use the 50%, 50% origin every other browser uses. Below you’ll see a function that uses the matrix we calculated to approximate a transform-origin(50%, 50%) in IE. Again, we’re hard-coding as much as possible to make this code easier to understand.

function rotate(elem, val) {
    var $elem = $(elem);
    ...
    // use the Sylvester $M() function to create a matrix
    var matrix = $M([
      [a, c, tx],
      [b, d, ty],
      [0, 0, 1]
    ]);

    // force an origin of 50%, 50%
    transformOrigin($elem, matrix);
}

function transformOrigin($elem, matrix) {
    // undo the filter
    var filter = $elem[0].style.filter;
    $elem[0].style.filter = '';

    // measure the element
    var width = $elem.outerWidth();
    var height = $elem.outerHeight();

    // re-do the filter
    $elem[0].style.filter = filter;

    // The destination origin
    toOrigin = {
        x: width * 0.5,
        y: height * 0.5
    };

    // The original origin
    fromOrigin = {
        x: 0,
        y: 0
    };

    // Multiply our rotation matrix against an x, y coord matrix
    var toCenter = matrix.x($M([
        [toOrigin.x],
        [toOrigin.y],
        [1]
    ]));
    var fromCenter = matrix.x($M([
        [fromOrigin.x],
        [fromOrigin.y],
        [1]
    ]));

    // Position the element
    // The double parse float simply keeps the decimals sane
    $elem.css({
        position: 'relative',
        top: parseFloat(parseFloat((fromCenter.e(2, 1) - fromOrigin.y) - (toCenter.e(2, 1) - toOrigin.y)).toFixed(8)) + 'px',
        left: parseFloat(parseFloat((fromCenter.e(1, 1) - fromOrigin.x) - (toCenter.e(1, 1) - toOrigin.x)).toFixed(8)) + 'px'
    });
}
Clone this wiki locally