By frenchie


2013-03-03 21:22:44 8 Comments

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.

7 comments

@yckart 2013-03-03 21:29:14

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

http://jsfiddle.net/UB2XR/23/

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 () {}
});

@John Dvorak 2013-03-03 21:30:47

(+ prefixes, ofc ;-) )

@frenchie 2013-03-03 21:32:11

Can you put this in a fiddle?

@yckart 2013-03-03 21:33:52

@frenchie I'm on iPhone, no chance since jsFiddle updated their editor ;) however, I'll prepare a bin for you...

@frenchie 2013-03-03 21:34:48

ok, it's just that I'm confused as to how to use it: $('#SomeDiv)...

@yckart 2013-03-03 21:39:24

@frenchie I forgot something! Updated my answer now... Here's an working example: jsbin.com/ofagog/2/edit

@frenchie 2013-03-04 00:36:45

Now I understand the concept of the step function! Thank you!!!!

@yckart 2013-03-04 01:34:40

@frenchie ok, too late, but if you don't understand an answer just ask ;) we're here to help!

@frenchie 2013-03-04 02:10:43

@yckart: no problem, I think your solution is awesome; just made an edit to include the duration control. Thanks again for your help!

@yckart 2013-03-04 07:23:22

@frenchie yeah, good point :) I've made some updates, maybe it helps you for future usages.

@frenchie 2013-03-04 11:54:52

Ok, very cool: that is THE plugin for cross-browser (IE9+) CSS3 rotation!! You can claim that: you built that. Nice work!

@Yeti 2013-10-17 11:30:42

I 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.

@Codebeat 2014-02-24 06:56:09

Why not a css class solution?

@matteo 2014-06-29 20:16:52

Hey 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?

@matteo 2014-06-29 20:22:10

@Trevin Avery 2014-10-30 15:08:54

What are $.speed() and $.style()? I can't find any documentation on these two functions.

@yckart 2014-10-30 17:04:29

@TrevinAvery jQuery.speed handles the arguments you pass into jQuery.fn.animate, it makes the order insignificant. (So, you can pass in duration, easing, complete or easing, duration, complete or whatever). jQuery.style is primarily the same as the already known jQuery.fn.css, however it works directly on the node instead of an jQuery-object. (jQuery.style is not an alias for jQuery.fn.css but is called in it.)

@yckart 2014-10-30 18:15:51

@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 2014-10-30 20:02:38

@matteo The reason this does not refer to a DOM object is because the context is set to the object animate() 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 with apply()/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 2014-10-30 22:34:32

Cool! Oh, I have to enter at least 15 characters. That's really cool!

@yckart 2014-12-15 01:09:42

@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 2015-01-21 13:17:20

If 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 2016-02-08 06:34:34

Can 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 2016-03-29 18:32:05

wow this is terrible, how about rotating back to zero? Oh, instant?

@yckart 2016-03-29 19:31:59

@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 2017-03-22 19:01:52

The binding with proxy inside of the loop is very expensive, so better use bind outside or call/apply.

@yckart 2017-03-23 06:02:02

@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.

@Seano 2017-06-15 10:14:03

For 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 2017-06-15 10:27:55

@Seano That's because of the deg: 0 part in those functions. You could easily adopt my code and change that 0 value to any other number.

@Tires 2017-03-22 18:57:35

Another answer, because jQuery.transit is not compatible with jQuery.easing. This solution comes as an jQuery extension. Is more generic, rotation is a specific case:

$.fn.extend({
    animateStep: function(options) {
        return this.each(function() {
            var elementOptions = $.extend({}, options, {step: options.step.bind($(this))});
            $({x: options.from}).animate({x: options.to}, elementOptions);
        });
    },
    rotate: function(value) {
        return this.css("transform", "rotate(" + value + "deg)");
    }
});

The usage is as simple as:

$(element).animateStep({from: 0, to: 90, step: $.fn.rotate});

@Alexey Alexeenka 2016-03-15 10:13:08

Without plugin cross browser with setInterval:

                        function rotatePic() {
                            jQuery({deg: 0}).animate(
                               {deg: 360},  
                               {duration: 3000, easing : 'linear', 
                                 step: function(now, fx){
                                   jQuery("#id").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)'
                                  });
                              }
                            });
                        }

                        var sec = 3;
                        rotatePic();
                        var timerInterval = setInterval(function() {
                            rotatePic();
                            sec+=3;
                            if (sec > 30) {
                                clearInterval(timerInterval);
                            }
                        }, 3000);

@AntiCampeR 2015-05-09 17:54:05

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' });

@Theo.T 2013-03-03 21:38:16

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 ?

@yckart 2013-03-03 21:44:18

This is not an answer...

@Yeti 2013-10-17 11:27:52

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.

@Liam 2013-10-18 10:16:18

That fiddle doesn't work in Chrome, at all.

@Liam 2013-10-18 10:17:51

Or IE9, does in Firefox, but only firefox.

@Yeti 2014-02-10 11:25:24

Okay 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.

@drabname 2013-07-10 19:23:44

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
        });
    });
};

@yckart 2013-07-23 13:43:15

jQuery adds the needed vendor prefix automatically, so no need for this!

@Pax Maximinus 2013-08-27 09:54:33

+1 for the cross platform. Great. @yckart : the auto prefix doesn't work for me in this case.

@yckart 2013-08-27 10:25:40

@PaxMaximinus What jQuery-version do you use? blog.jquery.com/2012/08/09/jquery-1-8-released

@Pax Maximinus 2013-08-28 08:33:07

@yckart : the 1.7.1 version.

@yckart 2013-08-28 09:01:35

@PaxMaximinus As you can see in the article from jquery-blog, the auto-prefixing is just since jquery-1.8+!

@Pax Maximinus 2013-08-28 09:21:55

@yckart : Thank you for the tip !

@whyAto8 2013-09-11 06:57:23

Hey, 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

@Amit Kumar Gupta 2018-01-28 03:57:11

This 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.

Related Questions

Sponsored Content

19 Answered Questions

[SOLVED] Is it possible to apply CSS to half of a character?

38 Answered Questions

[SOLVED] How do I vertically center text with CSS?

31 Answered Questions

[SOLVED] Is there a CSS parent selector?

  • 2009-06-18 19:59:36
  • jcuenod
  • 1745691 View
  • 2895 Score
  • 31 Answer
  • Tags:   css css-selectors

34 Answered Questions

[SOLVED] Change an HTML5 input's placeholder color with CSS

31 Answered Questions

[SOLVED] How to make a div 100% height of the browser window

  • 2009-10-15 21:18:43
  • mike
  • 1793212 View
  • 1951 Score
  • 31 Answer
  • Tags:   html css height

39 Answered Questions

[SOLVED] How to align checkboxes and their labels consistently cross-browsers

28 Answered Questions

[SOLVED] How do I give text or an image a transparent background using CSS?

  • 2009-04-30 09:00:02
  • Stijn Sanders
  • 1508174 View
  • 2172 Score
  • 28 Answer
  • Tags:   html css opacity

31 Answered Questions

[SOLVED] Activity restart on rotation Android

26 Answered Questions

[SOLVED] Set cellpadding and cellspacing in CSS?

16 Answered Questions

[SOLVED] When to use margin vs padding in CSS

  • 2010-02-03 03:20:12
  • Alex Angas
  • 736404 View
  • 2215 Score
  • 16 Answer
  • Tags:   css padding margin

Sponsored Content