web-dev-qa-db-fra.com

Rotation CSS cross browser avec jquery.animate ()

Je travaille sur la création d'une rotation compatible avec plusieurs navigateurs (ie9 +) et j'ai le code suivant dans 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); 
}

Le CSS et le HTML sont vraiment simples et juste pour la démo:

.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 rotation fonctionne avec .css() mais pas avec .animate(); pourquoi est-ce et y at-il un moyen de résoudre ce problème?

Merci.

79
frenchie

Les transformations CSS ne peuvent pas encore s'animer avec jQuery. Vous pouvez faire quelque chose comme ça:

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

Vous pouvez en savoir plus sur le rappel par étapes ici: http://api.jquery.com/animate/#step

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

Et, d'ailleurs: vous n'avez pas besoin de préfixer les transformations css3 avec jQuery 1.7+

Mise à jour

Vous pouvez envelopper cela dans un plugin jQuery pour vous simplifier la vie:

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

Mise à jour2

Je l'ai optimisé un peu pour rendre la commande de easing, duration et complete insignifiante.

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

Mise à jour 2.1

Merci à matteo qui a signalé un problème avec le contexte this- dans la version complète_callback. Si elle est corrigée par , liez le rappel avec jQuery.proxy sur chaque nœud.

J'ai ajouté l'édition au code avant de Update 2 .

Mise à jour 2.2

Ceci est une modification possible si vous voulez faire quelque chose comme basculer la rotation dans les deux sens. J'ai simplement ajouté un paramètre de début à la fonction et remplacé cette ligne:

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

Si quelqu'un sait comment rendre cela plus générique pour tous les cas d'utilisation, qu'il veuille ou non définir un degré de début, veuillez faire la modification appropriée.


L'usage... est assez simple!

Vous avez principalement deux moyens pour atteindre le résultat souhaité. Mais au premier abord, examinons les arguments:

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

À l'exception de "angle", ils sont tous facultatifs et se replient sur les propriétés jQuery.fn.animate- par défaut:

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

1er

Cette façon de faire est la plus courte, mais il semble que les arguments que nous transmettons soient plus nombreux.

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

2ème

Je préfère utiliser des objets s'il y a plus de trois arguments, cette syntaxe est donc ma préférée:

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

Merci Yckart! Grande contribution. J'ai étoffé votre plugin un peu plus. Ajout de startAngle pour un contrôle total et des CSS inter-navigateurs.

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

jQuery transit vous facilitera probablement la vie si vous utilisez des animations CSS3 via jQuery.

ÉDITER mars 2014 (parce que mes conseils ont été constamment inversés voté depuis que je l'ai posté)

Laissez-moi vous expliquer pourquoi je faisais initialement allusion au plugin ci-dessus:

Mettre à jour la DOM à chaque étape (c'est-à-dire $.animate) n'est pas idéal en termes de performances. Cela fonctionne, mais sera probablement plus lent que pur transitions CSS ou animations CSS .

Cela est principalement dû au fait que le navigateur a la possibilité de penser à l’avenir si vous indiquez à quoi ressemblera la transition du début à la fin.

Pour ce faire, vous pouvez par exemple créer une classe CSS pour chaque état de la transition et utiliser uniquement jQuery pour basculer l'état d'animation.

C’est généralement très bien car vous pouvez modifier vos animations avec le reste de votre code CSS au lieu de les mélanger à votre logique d’entreprise:

// 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 l'un des paramètres de transformation est dynamique, vous pouvez bien sûr utiliser l'attribut style à la place:

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

Des informations beaucoup plus détaillées sur transitions CSS3 sur MDN .

CEPENDANT Il y a quelques autres choses à garder à l'esprit et tout cela peut devenir un peu délicat si vous avez des animations complexes, des chaînages, etc. et - jQuery Transit fait tout ce qui est délicat sous le capot:

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

Pour faire cela avec plusieurs navigateurs, y compris IE7 +, vous devrez développer le plugin avec une matrice de transformation. Comme le préfixe du vendeur est défini dans jQuery à partir de jquery-1.8 +, je le laisserai de côté pour la propriété 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);
        }
    });
};

Remarque: Les paramètres options et startAngle sont facultatifs. Si vous devez uniquement définir startAngle, utilisez {} ou null pour options.

Exemple d'utilisation:

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

Voir aussi ceci jsfiddle pour une démo.

Mise à jour : Vous pouvez maintenant également passer extra: {} dans les options. Cela vous permettra d'exécuter d'autres animations simultanément. Par exemple:

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

Cela fera pivoter l'élément de 90 degrés, le déplacera vers la droite de 100 pixels et le rendra semi-transparent en même temps pendant l'animation.

3
Yeti

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

alors vous pouvez l'utiliser dans la fkt animate par défaut:

//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
AntiCampeR

Une autre réponse, car jQuery.transit n'est pas compatible avec jQuery.easing. Cette solution est une extension jQuery. Est plus générique, la rotation est un cas spécifique:

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

L'utilisation est aussi simple que:

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

Sans plugin, navigateur croisé avec 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
Alexey Alexeenka