Rotating the Microsoft way
One of the main challenges in putting together the 'virtual record player' demo was how to program the rotating record so that it would look reasonably convincing. The latest flavour of CSS, version 3, will eventually cater for rotation, skewing, and other complex graphical transformations, but at the time of writing (June 2010) each of the rendering engines used by web browsers has its own set of instructions for rotation. Each of these has to be provided as CSS; each browser will apply the instructions that its rendering engine understands, and ignore the others.
-webkit-transform: rotate(30deg);
-o-transform: rotate(30deg);
-moz-transform: rotate(30deg);
These three instructions are effected by the Webkit (=Chrome, Safari), Opera and Mozilla (=Firefox) rendering
engines respectively, and each performs the rotation about the object's geographical centre. Figure 1
shows the result of applying this instruction to a square: the original object (blue) is shown with a rotated
copy (red).

cos A -sin A
sin A cos A
If A is 30°, then these four values, reading across then down, are:
0.8660254037844387 -0.5
0.5 0.8660254037844387
In order to display the whole of the rotated object, and not just a clipped part the
size of the original dimensions, we have to specify
sizingMethod='auto expand'
The instruction to IE to rotate through 30° will then look like this:
progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand',
"M11=0.8660254037844387, M12=0.5,
M21=0.5, M22=0.8660254037844387");
So to do this for any angle, we need a Javascript function that can return the appropriate trigonometrical ratios,
given a desired angle. Here it is. Note that Javascript trigonometrical functions take the angle in radians: there are 2π
radians in 360 degrees, and so a radian works out at about 57°. This conversion is carried out in the second line of
the function.
function ieSetRotationArray(A) {
var MMatrix= new Array();
var rad = A / (180 / Math.PI);
var cosA = Math.cos(rad);
var sinA = Math.sin(rad);
MMatrix[0] = cosA; // M11
MMatrix[1] = -sinA; // M12
MMatrix[2] = sinA; // M21
MMatrix[3] = cosA; // M22
return MMatrix;
}


A and a side length l:
l * (sin A + cos A - 1) / 2
As the image is a square, the same amount should be subtracted from the left and top property values.
In this case, the amount is approximately 18.3.
-webkit-transform-origin: 40% 33.3%;
-o-transform-origin: 40% 33.3%;
-moz-transform-origin: 40% 33.3%;
For IE, though, the calculations become more complex. In the following example, we are attempting to rotate an oblong
50 units by 150, through 30°, around an axis situated 40% of the way across the shape and one-third of the way down.
Applying the matrix derived above, the Microsoft transform yields a result like that in figure 5:


Math.atan2(y,x) which gives the
angle between an origin at (0,0) and the point (x,y), and we can use this to work out the angle from the geographic centre
G to the desired rotational centre R:
angle = Math.atan2(Ry-Gy,Rx-Gx);
The distance is readily calculated using Pythagoras' theorem. In Javascript, this looks like:
distance = Math.sqrt(Math.pow(Ry-Gy,2) + Math.pow(Rx-Gx,2));
If we plug our coordinates into these two formulas, we get the angle as approximately 258.7° and the
distance as 25.5 units.
width = |h sin A| + |w cos A|
height= |w sin A| + |h cos A|
where w and h are the original width and height. From here, we find R' by simply measuring off
25.5 units on a bearing of 288.7° from G', using the following formulas:
R'x = distance * cos (new angle) + Gx
R'y = distance * sin (new angle) + Gy
Again, in our example this puts R' at (67.3, 53.3). Finally, the left and top subtrahends
needed to shift the rotated oblong, in order to bring R' back to R, are given by the difference between those two sets
of coordinates. Thus, we need to shift left by 47.3 units, and up by 3.3.
function microsoftRotation(vObjectId,vAngle,vPivotXPct,vPivotYPct) {
// Create reference to original object
var obj=document.getElementById(vObjectId);
// Find out its width and height
var objWidth=parseInt(obj.style.width);
var objHeight=parseInt(obj.style.height);
// Work out where the axis of rotation (pivot) is, given
// the desired percentage across and down
var pivotX=(objWidth * vPivotXPct/100);
var pivotY=(objHeight * vPivotYPct/100);
// Calculate the DXImageTransform matrix from function (q.v.)
var ieRotationMatrix=ieSetRotationArray(vAngle);
// Calculate the angle and distance from the geographic
// centre of the object to the desired axis of rotation
var angleGeogCentreToPivotRAD = Math.atan2(pivotY-objHeight/2,pivotX-objWidth/2);
var distGeogCentreToPivot =
Math.sqrt(Math.pow(objHeight/2 - pivotY,2) + Math.pow(objWidth/2 - pivotX,2));
// Establish the dimensions of the rotated object's
// new 'envelope', by trigonometry
var newWidth=Math.abs(ieRotationMatrix[2]*objHeight) + Math.abs(ieRotationMatrix[3]*objWidth);
var newHeight=Math.abs(ieRotationMatrix[2]*objWidth) + Math.abs(ieRotationMatrix[3]*objHeight);
// Find new geographical centre of 'envelope'
var newGeogCentreX=newWidth / 2;
var newGeogCentreY=newHeight / 2;
// Add desired rotation angle to angle found earlier (in radians)
var newAngleGeogCentreToPivotRAD= vAngle * Math.PI / 180 + angleGeogCentreToPivotRAD;
// Find new axis of rotation by measuring out from
// the new geographic centre by the distance found
// earlier, at the new angle
var newPivotX=Math.cos(newAngleGeogCentreToPivotRAD) * distGeogCentreToPivot + newGeogCentreX;
var newPivotY=Math.sin(newAngleGeogCentreToPivotRAD) * distGeogCentreToPivot + newGeogCentreY;
// Find required offsets by subtracting original from new pivot coordinates
var ieOffsetX=newPivotX - pivotX;
var ieOffsetY=newPivotY - pivotY;
// String together the parameters for DXImageTransform
var ieMFigures="M11="+ieRotationMatrix[0]+", M12="+ieRotationMatrix[1]
+", M21="+ieRotationMatrix[2]+", M22="+ieRotationMatrix[3];
// Shift rotated image left and up by the offsets found
obj.zoom=1;
obj.style.left=(origLeft-ieOffsetX) + "px";
obj.style.top=(origTop-ieOffsetY) + "px";
obj.style.filter="progid:DXImageTransform.Microsoft.Matrix(sizingMethod='auto expand', "+ieMFigures+") ";
}
Back to previous page