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

Estoy trabajando en la creación de una rotación compatible con varios navegadores (es decir, 9 +) y tengo el siguiente código en 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 demostración:

 .SomeDiv{ width:50px; height:50px; margin:50px 50px; background-color: red;} 
test
test

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

Gracias.

CSS-Transforms aún no es posible animar 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)' }); } }); } 

Puedes leer más acerca de la callback aquí: http://api.jquery.com/animate/#step

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

Y, por cierto, no es necesario prefijar las transformaciones css3 con jQuery 1.7+

Actualizar

Puede envolver esto en un jQuery-plugin 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 que el orden 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 que notó un problema con el this -contexto en la callback completa. Si se solucionó al vincular la callback con jQuery.proxy en cada nodo.

He agregado la edición al código antes de la Actualización 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 reemplacé esta línea:

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

Si alguien sabe cómo hacer que esto sea más genérico para todos los casos de uso, independientemente de si desean o no establecer un grado de inicio, realice la edición adecuada.


El uso … ¡es bastante simple!

Principalmente tienes dos formas de alcanzar el resultado deseado. Pero al principio, echemos un vistazo a los argumentos:

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

Excepto por “ángulo”, todos son opcionales y jQuery.fn.animate vuelta en el jQuery.fn.animate -properties predeterminado:

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

1ra

De esta manera es corto, pero parece un poco confuso cuanto más argumentos se transmiten.

 $(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 syntax es mi favor:

 $(node).animateRotate(90, { duration: 1337, easing: 'linear', complete: function () {}, step: function () {} }); 

Gracias yckart! Gran contribución Completé tu plugin un poco más. Se ha agregado 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 }); }); }; 

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

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

Permítanme explicar por qué estaba inicialmente insinuando hacia el complemento anterior:

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

Esto se debe principalmente a que el navegador tiene la oportunidad de pensar en el futuro si usted 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 ordenado ya que puede modificar sus animaciones junto con el rest 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 dynamic, puede usar el atributo de estilo 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)' }); }); 

Información mucho más detallada sobre las transiciones de CSS3 en MDN .

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

 $('.eye').transit({ rotate: '90deg'}); // easy huh ? 

Para hacer este navegador cruzado que incluye IE7 +, deberá expandir el complemento con una matriz de transformación. Dado que el prefijo del proveedor se hace en jQuery desde jquery-1.8 + lo dejaré para la propiedad de 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 + '
'); 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: Las options parámetros y startAngle son opcionales, si solo necesita configurar startAngle use {} o null para las 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); 

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

Actualización : ahora también puede pasar extra: {} en las opciones. Esto te permitirá ejecutar otras animaciones simultáneamente. Por ejemplo:

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

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

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 puedes usarlo en el fkt animado predeterminado:

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

Otra respuesta, porque jQuery.transit no es compatible con jQuery.easing. 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}); 

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