CSS rotation cross browser with jquery.animate()
Solution 1
CSS-Transforms are not possible to animate with jQuery, yet. You can do something like this:
function AnimateRotate(angle) {
// caching the object for performance reasons
var $elem = $('#MyDiv2');
// we use a pseudo object for the animation
// (starts from `0` to `angle`), you can name it as you want
$({deg: 0}).animate({deg: angle}, {
duration: 2000,
step: function(now) {
// in the step-callback (that is fired each step of the animation),
// you can use the `now` paramter which contains the current
// animation-position (`0` up to `angle`)
$elem.css({
transform: 'rotate(' + now + 'deg)'
});
}
});
}
You can read more about the step-callback here: http://api.jquery.com/animate/#step
And, btw: you don't need to prefix css3 transforms with jQuery 1.7+
Update
You can wrap this in a jQuery-plugin to make your life a bit easier:
$.fn.animateRotate = function(angle, duration, easing, complete) {
return this.each(function() {
var $elem = $(this);
$({deg: 0}).animate({deg: angle}, {
duration: duration,
easing: easing,
step: function(now) {
$elem.css({
transform: 'rotate(' + now + 'deg)'
});
},
complete: complete || $.noop
});
});
};
$('#MyDiv2').animateRotate(90);
http://jsbin.com/ofagog/2/edit
Update2
I optimized it a bit to make the order of easing
, duration
and complete
insignificant.
$.fn.animateRotate = function(angle, duration, easing, complete) {
var args = $.speed(duration, easing, complete);
var step = args.step;
return this.each(function(i, e) {
args.complete = $.proxy(args.complete, e);
args.step = function(now) {
$.style(e, 'transform', 'rotate(' + now + 'deg)');
if (step) return step.apply(e, arguments);
};
$({deg: 0}).animate({deg: angle}, args);
});
};
Update 2.1
Thanks to matteo who noted an issue with the this
-context in the complete-callback
. If fixed it by binding the callback with jQuery.proxy
on each node.
I've added the edition to the code before from Update 2.
Update 2.2
This is a possible modification if you want to do something like toggle the rotation back and forth. I simply added a start parameter to the function and replaced this line:
$({deg: start}).animate({deg: angle}, args);
If anyone knows how to make this more generic for all use cases, whether or not they want to set a start degree, please make the appropriate edit.
The Usage...is quite simple!
Mainly you've two ways to reach the desired result. But at the first, let's take a look on the arguments:
jQuery.fn.animateRotate(angle, duration, easing, complete)
Except of "angle" are all of them optional and fallback to the default jQuery.fn.animate
-properties:
duration: 400
easing: "swing"
complete: function () {}
1st
This way is the short one, but looks a bit unclear the more arguments we pass in.
$(node).animateRotate(90);
$(node).animateRotate(90, function () {});
$(node).animateRotate(90, 1337, 'linear', function () {});
2nd
I prefer to use objects if there are more than three arguments, so this syntax is my favorit:
$(node).animateRotate(90, {
duration: 1337,
easing: 'linear',
complete: function () {},
step: function () {}
});
Solution 2
Thanks yckart! Great contribution. I fleshed out your plugin a bit more. Added startAngle for full control and cross-browser css.
$.fn.animateRotate = function(startAngle, endAngle, duration, easing, complete){
return this.each(function(){
var elem = $(this);
$({deg: startAngle}).animate({deg: endAngle}, {
duration: duration,
easing: easing,
step: function(now){
elem.css({
'-moz-transform':'rotate('+now+'deg)',
'-webkit-transform':'rotate('+now+'deg)',
'-o-transform':'rotate('+now+'deg)',
'-ms-transform':'rotate('+now+'deg)',
'transform':'rotate('+now+'deg)'
});
},
complete: complete || $.noop
});
});
};
Solution 3
jQuery transit will probably make your life easier if you are dealing with CSS3 animations through jQuery.
EDIT March 2014 (because my advice has constantly been up and down voted since I posted it)
Let me explain why I was initially hinting towards the plugin above:
Updating the DOM
on each step (i.e. $.animate
) is not ideal in terms of performance.
It works, but will most probably be slower than pure CSS3 transitions or CSS3 animations.
This is mainly because the browser gets a chance to think ahead if you indicate what the transition is going to look like from start to end.
To do so, you can for example create a CSS class for each state of the transition and only use jQuery to toggle the animation state.
This is generally quite neat as you can tweak you animations alongside the rest of your CSS instead of mixing it up with your business logic:
// initial state
.eye {
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
transform: rotate(45deg);
// etc.
// transition settings
-webkit-transition: -webkit-transform 1s linear 0.2s;
-moz-transition: -moz-transform 1s linear 0.2s;
transition: transform 1s linear 0.2s;
// etc.
}
// open state
.eye.open {
transform: rotate(90deg);
}
// Javascript
$('.eye').on('click', function () { $(this).addClass('open'); });
If any of the transform parameters is dynamic you can of course use the style attribute instead:
$('.eye').on('click', function () {
$(this).css({
-webkit-transition: '-webkit-transform 1s ease-in',
-moz-transition: '-moz-transform 1s ease-in',
// ...
// note that jQuery will vendor prefix the transform property automatically
transform: 'rotate(' + (Math.random()*45+45).toFixed(3) + 'deg)'
});
});
A lot more detailed information on CSS3 transitions on MDN.
HOWEVER There are a few other things to keep in mind and all this can get a bit tricky if you have complex animations, chaining etc. and jQuery Transit just does all the tricky bits under the hood:
$('.eye').transit({ rotate: '90deg'}); // easy huh ?
Solution 4
To do this cross browser including IE7+, you will need to expand the plugin with a transformation matrix. Since vendor prefix is done in jQuery from jquery-1.8+ I will leave that out for the transform
property.
$.fn.animateRotate = function(endAngle, options, startAngle)
{
return this.each(function()
{
var elem = $(this), rad, costheta, sintheta, matrixValues, noTransform = !('transform' in this.style || 'webkitTransform' in this.style || 'msTransform' in this.style || 'mozTransform' in this.style || 'oTransform' in this.style),
anims = {}, animsEnd = {};
if(typeof options !== 'object')
{
options = {};
}
else if(typeof options.extra === 'object')
{
anims = options.extra;
animsEnd = options.extra;
}
anims.deg = startAngle;
animsEnd.deg = endAngle;
options.step = function(now, fx)
{
if(fx.prop === 'deg')
{
if(noTransform)
{
rad = now * (Math.PI * 2 / 360);
costheta = Math.cos(rad);
sintheta = Math.sin(rad);
matrixValues = 'M11=' + costheta + ', M12=-'+ sintheta +', M21='+ sintheta +', M22='+ costheta;
$('body').append('Test ' + matrixValues + '<br />');
elem.css({
'filter': 'progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\','+matrixValues+')',
'-ms-filter': 'progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\','+matrixValues+')'
});
}
else
{
elem.css({
//webkitTransform: 'rotate('+now+'deg)',
//mozTransform: 'rotate('+now+'deg)',
//msTransform: 'rotate('+now+'deg)',
//oTransform: 'rotate('+now+'deg)',
transform: 'rotate('+now+'deg)'
});
}
}
};
if(startAngle)
{
$(anims).animate(animsEnd, options);
}
else
{
elem.animate(animsEnd, options);
}
});
};
Note: The parameters options
and startAngle
are optional, if you only need to set startAngle
use {}
or null
for options
.
Example usage:
var obj = $(document.createElement('div'));
obj.on("click", function(){
obj.stop().animateRotate(180, {
duration: 250,
complete: function()
{
obj.animateRotate(0, {
duration: 250
});
}
});
});
obj.text('Click me!');
obj.css({cursor: 'pointer', position: 'absolute'});
$('body').append(obj);
See also this jsfiddle for a demo.
Update: You can now also pass extra: {}
in the options. This will make you able to execute other animations simultaneously. For example:
obj.animateRotate(90, {extra: {marginLeft: '100px', opacity: 0.5}});
This will rotate the element 90 degrees, and move it to the right with 100px and make it semi-transparent all at the same time during the animation.
Solution 5
this is my solution:
var matrixRegex = /(?:matrix\(|\s*,\s*)([-+]?[0-9]*\.?[0-9]+(?:[e][-+]?[0-9]+)?)/gi;
var getMatches = function(string, regex) {
regex || (regex = matrixRegex);
var matches = [];
var match;
while (match = regex.exec(string)) {
matches.push(match[1]);
}
return matches;
};
$.cssHooks['rotation'] = {
get: function(elem) {
var $elem = $(elem);
var matrix = getMatches($elem.css('transform'));
if (matrix.length != 6) {
return 0;
}
return Math.atan2(parseFloat(matrix[1]), parseFloat(matrix[0])) * (180/Math.PI);
},
set: function(elem, val){
var $elem = $(elem);
var deg = parseFloat(val);
if (!isNaN(deg)) {
$elem.css({ transform: 'rotate(' + deg + 'deg)' });
}
}
};
$.cssNumber.rotation = true;
$.fx.step.rotation = function(fx) {
$.cssHooks.rotation.set(fx.elem, fx.now + fx.unit);
};
then you can use it in the default animate fkt:
//rotate to 90 deg cw
$('selector').animate({ rotation: 90 });
//rotate to -90 deg ccw
$('selector').animate({ rotation: -90 });
//rotate 90 deg cw from current rotation
$('selector').animate({ rotation: '+=90' });
//rotate 90 deg ccw from current rotation
$('selector').animate({ rotation: '-=90' });
frenchie
Started programming when I was 10. Never made it my profession but always programmed, mostly with Excel VBA for finance, and for some simple front-end web design in the early 2000's (ie. no jquery back then, what a pain it was with DHTML). One day, I get the idea of outsourcing the web development of my start-up: total cacacacacacacafukkkk! So: from VBA to C# .NET and Javascript in 5,000 pages of programming books, and lots of Stackoverflow! I think asking dumb questions is a smart way to learn. And as an internet entrepreneur, I've learned that what matters most is not knowing how to program but knowing what to program: discover the goyaPhone at www.goyaphone.eu
Updated on July 08, 2022Comments
-
frenchie almost 2 years
I'm working on creating a cross-browser compatible rotation (ie9+) and I have the following code in a jsfiddle
$(document).ready(function () { DoRotate(30); AnimateRotate(30); }); function DoRotate(d) { $("#MyDiv1").css({ '-moz-transform':'rotate('+d+'deg)', '-webkit-transform':'rotate('+d+'deg)', '-o-transform':'rotate('+d+'deg)', '-ms-transform':'rotate('+d+'deg)', 'transform': 'rotate('+d+'deg)' }); } function AnimateRotate(d) { $("#MyDiv2").animate({ '-moz-transform':'rotate('+d+'deg)', '-webkit-transform':'rotate('+d+'deg)', '-o-transform':'rotate('+d+'deg)', '-ms-transform':'rotate('+d+'deg)', 'transform':'rotate('+d+'deg)' }, 1000); }
The CSS and HTML are really simple and just for demo:
.SomeDiv{ width:50px; height:50px; margin:50px 50px; background-color: red;} <div id="MyDiv1" class="SomeDiv">test</div> <div id="MyDiv2" class="SomeDiv">test</div>
The rotation works when using
.css()
but not when using.animate()
; why is that and is there a way to fix it?Thanks.
-
frenchie about 11 yearsCan you put this in a fiddle?
-
yckart about 11 years@frenchie I'm on iPhone, no chance since jsFiddle updated their editor ;) however, I'll prepare a bin for you...
-
yckart about 11 years@frenchie I forgot something! Updated my answer now... Here's an working example: jsbin.com/ofagog/2/edit
-
frenchie about 11 yearsOk, very cool: that is THE plugin for cross-browser (IE9+) CSS3 rotation!! You can claim that: you built that. Nice work!
-
yckart almost 11 yearsjQuery adds the needed vendor prefix automatically, so no need for this!
-
lsmpascal over 10 years+1 for the cross platform. Great. @yckart : the auto prefix doesn't work for me in this case.
-
yckart over 10 years@PaxMaximinus What jQuery-version do you use? blog.jquery.com/2012/08/09/jquery-1-8-released
-
lsmpascal over 10 years@yckart : the 1.7.1 version.
-
yckart over 10 years@PaxMaximinus As you can see in the article from jquery-blog, the auto-prefixing is just since
jquery-1.8+
! -
whyAto8 over 10 yearsHey, does this solution works for IE8 as well? If not, what solution can be used for IE8, I just saw one of the options as jquery rotate plugin
-
Yeti over 10 yearsI made this plugin work for IE7+ using transform matrix, also I improved the parameters. You really don't want to pass just and exactly those parameters.
-
Liam over 10 yearsOr IE9, does in Firefox, but only firefox.
-
Yeti about 10 yearsOkay so it works now in Chrome, Firefox and IE10. Can you test IE9, Liam? The problem was that the transform property was undefined for Chrome and IE, therefore the script thought that the transform property was unavailable. Hence, I changed the script to include all the prefixes:
ms
,o
,webkit
,moz
to ensure detection correctly. The fiddle is updated as well to v12. -
Codebeat about 10 yearsWhy not a css class solution?
-
matteo almost 10 yearsHey there's something wrong at least with the "update 2" version. When the complete function is executed, "this" inside it is supposed to be the dom element, but it is some other object. Any idea how to fix this?
-
Trevin Avery over 9 yearsWhat are
$.speed()
and$.style()
? I can't find any documentation on these two functions. -
yckart over 9 years@TrevinAvery
jQuery.speed
handles the arguments you pass intojQuery.fn.animate
, it makes the order insignificant. (So, you can pass induration, easing, complete
oreasing, duration, complete
or whatever).jQuery.style
is primarily the same as the already knownjQuery.fn.css
, however it works directly on the node instead of an jQuery-object. (jQuery.style
is not an alias forjQuery.fn.css
but is called in it.) -
yckart over 9 years@matteo Sorry for the late response and thanks for your test. I needed a little time fiddle the issue out, but I got it! fiddle.jshell.net/P5J4V/43 By the way, I mentioned your investigation in my anwer :)
-
Trevin Avery over 9 years@matteo The reason
this
does not refer to a DOM object is because the context is set to the objectanimate()
was called on, in this case{deg: 0}
is set to the context. You can fix this by changing the context of each callback function withapply()
/call()
or$.proxy()
(as @yckart has shown). Here is my solution to fix ALL callbacks and allow 3d rotation: jsfiddle.net/TrevinAvery/P5J4V/44 -
matteo over 9 yearsCool! Oh, I have to enter at least 15 characters. That's really cool!
-
yckart over 9 years@TrevinAvery Great work, I like the way you handle the promise-callbacks. However, I recognized a logic problem. Currently the styles are applied with CSS3-properties only, they are not supported by older browsers, so nothing will move around. Making another "type of string/object"-test and applying the styles, depending on this check, should fix it.
-
Asbjørn Ulsberg over 9 yearsIf you want to animate the same element over and over, starting on
0
degrees every time will not lead to the expected behavior, so you need to initialize with the current rotation value. How to do that is explained here: stackoverflow.com/a/11840120/61818 -
user308553 about 8 yearsCan someone explain the
$({deg: 0}).animate({deg: angle}
part to me. I don't understand how it would do 0 to angle. It works even angle is negative. 1) How does it know it should do deg++ or deg-- 2) how does $({deg:0}) not return an error, I thought $() is a selector that would look for an element that fit the criteria, and the criteria has to be a certain syntax. This is like magic to me -
raveren about 8 yearswow this is terrible, how about rotating back to zero? Oh, instant?
-
yckart about 8 years@Raveren This is an example and nothing to copy & paste, thoughtless... Just copy and code, modify and change. If you need help at some point, create a new question.
-
Tires about 7 yearsThe binding with proxy inside of the loop is very expensive, so better use bind outside or call/apply.
-
yckart about 7 years@Tires Good finding. Passing the actual iterated element as argument to the
complete
callback, that can not be done outside of the loop. One could easily drop it, if not required. Note, this not a full solution, use it as startingpoint for another. -
Sean H almost 7 yearsFor me, this method resets the start position to 0deg. So if I want to rotate an element which has a non-zero starting angle it doesn't work. Is anyone else having the same issue?
-
yckart almost 7 years@Seano That's because of the
deg: 0
part in those functions. You could easily adopt my code and change that0
value to any other number. -
Amit Kumar Gupta over 6 yearsThis solution doesn't work when you set some data to the element after the animation. So that you can continue the animation after some time. So I have took your solution and combined with @yckart solution to make it cross browser compatible.