Navegador cruzado de rotación CSS con jquery.animar()


Estoy trabajando en la creación de una rotación compatible entre navegadores (ie9+) y tengo el siguiente código en un 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); 
}

El CSS y HTML son realmente simples y solo para 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>

La rotación funciona cuando se usa .css() pero no cuando se usa .animate(); ¿por qué es eso y hay una manera de arreglarlo?

Gracias.

Author: frenchie, 2013-03-04

7 answers

Todavía no es posible animar las transformaciones CSS con jQuery. Puedes hacer algo como esto:

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

Puede leer más sobre la devolución de llamada paso a paso aquí: http://api.jquery.com/animate/#step

Http://jsfiddle.net/UB2XR/23 /

Y, por cierto: no necesita prefijar transformaciones css3 con jQuery 1.7+

Actualización

Puede envolver esto en un plugin de jQuery para hacer su vida un poco más fácil:

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

Actualización2

Lo optimicé un poco para hacer el pedido de easing, duration y complete insignificante.

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

Actualización 2.1

Gracias a matteo quien notó un problema con el this-contexto en el completo-callback. Si se fija por enlace la devolución de llamada con jQuery.proxy en cada nodo.

He añadido la edición al código anterior desde Actualizar 2.

Actualización 2.2

Esta es una posible modificación si desea hacer algo como alternar la rotación hacia adelante y hacia atrás. Simplemente agregué un parámetro de inicio a la función y reemplazé esta línea:

$({deg: start}).animate({deg: angle}, args);

Si alguien sabe cómo hacer esto más genérico para todos los casos de uso, quiera o no establecer un grado de inicio, por favor haga la edición apropiada.


El uso is es bastante simple!

Principalmente tienes dos maneras de llegar a la resultado deseado. Pero al principio, echemos un vistazo a los argumentos:

jQuery.fn.animateRotate(angle, duration, easing, complete)

Excepto de "ángulo" son todos opcionales y de reserva a la por defecto jQuery.fn.animate-propiedades:

duration: 400
easing: "swing"
complete: function () {}

1st

De esta manera es el corto, pero parece un poco confuso cuantos más argumentos pasamos.

$(node).animateRotate(90);
$(node).animateRotate(90, function () {});
$(node).animateRotate(90, 1337, 'linear', function () {});

2nd

Prefiero usar objetos si hay más de tres argumentos, por lo que esta sintaxis es mi favorit:

$(node).animateRotate(90, {
  duration: 1337,
  easing: 'linear',
  complete: function () {},
  step: function () {}
});
 204
Author: yckart,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-25 22:42:46

Gracias yckart! Gran contribución. He desarrollado su plugin un poco más. Añadido startAngle para un control total y css entre navegadores.

$.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
        });
    });
};
 17
Author: drabname,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-10 19:23:44

JQuery transit probablemente le hará la vida más fácil si está tratando con animaciones CSS3 a través de jQuery.

EDITAR Marzo de 2014 (porque mi consejo ha sido votado constantemente desde que lo publiqué)

Permítanme explicar por qué estaba insinuando inicialmente hacia el plugin de arriba:

Actualizar el DOM en cada paso (es decir, $.animate) no es ideal en términos de rendimiento. Funciona, pero lo más probable es que sea más lento que puro transiciones CSS3 o animaciones CSS3.

Esto se debe principalmente a que el navegador tiene la oportunidad de pensar con anticipación si indica cómo se verá la transición de principio a fin.

Para hacerlo, puede, por ejemplo, crear una clase CSS para cada estado de la transición y solo usar jQuery para alternar el estado de la animación.

Esto es generalmente bastante limpio, ya que puede ajustar las animaciones junto con el resto de su CSS en lugar de mezclarlo con su lógica de negocio:

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

Si alguno de los parámetros de transformación es dinámico, por supuesto, puede usar el atributo style en su lugar:

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

Mucha más información detallada sobre Transiciones de CSS3 en MDN.

SIN EMBARGO Hay algunas otras cosas a tener en cuenta y todo esto puede ser un poco complicado si tiene animaciones complejas, encadenamiento, etc. y jQuery Transit simplemente hace todos los bits difíciles bajo el capó:

$('.eye').transit({ rotate: '90deg'}); // easy huh ?
 10
Author: Theo.T,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-26 13:44:18

Para hacer esto cross browser incluyendo IE7+, necesitará expandir el plugin con una matriz de transformación. Dado que el prefijo de proveedor se realiza en jQuery desde jquery-1.8 + dejaré eso fuera para la propiedad transform.

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

Nota: Los parámetros options y startAngle son opcionales, si sólo necesita establecer startAngle utilizar {} o null para options.

Ejemplo de uso:

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

Vea también este jsfiddle para una demostración.

Actualizar: Ahora también puede pasar extra: {} en las opciones. Esto te hará capaz de ejecutar otras animaciones simultáneamente. Por ejemplo:

obj.animateRotate(90, {extra: {marginLeft: '100px', opacity: 0.5}});

Esto rotará el elemento 90 grados, y lo moverá a la derecha con 100px y lo hará semitransparente todo al mismo tiempo durante la animación.

 3
Author: Yeti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-02-10 11:23:06

Esta es mi solución:

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

Entonces puede usarlo en el valor predeterminado de 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' });
 2
Author: AntiCampeR,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-09 18:39:16

Otra respuesta, porque jQuery.transit no es compatible con jQuery.aliviar. Esta solución viene como una extensión jQuery. Es más genérico, la rotación es un caso específico:

$.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)");
    }
});

El uso es tan simple como:

$(element).animateStep({from: 0, to: 90, step: $.fn.rotate});
 1
Author: Tires,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-17 14:25:28

Sin plugin cross browser con 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);
 0
Author: Alexey Alexeenka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-15 10:13:08