Кросс-браузер вращения CSS с jquery.animate ()

81

Я работаю над созданием кроссбраузерной ротации (ie9 +), и у меня есть следующий код в 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); 
}

CSS и HTML действительно просты и предназначены только для демонстрации:

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

Вращение работает при использовании, .css()но не при использовании .animate(); почему это так и есть ли способ это исправить?

Благодарю.

француженка
источник
jQuery не знает, как анимировать вращение. Возможно, использовать переходы CSS3?
John Dvorak
1
@JanDvorak - за исключением того, что IE9 не поддерживает переходы CSS3.
Spudley 03
1
Я буду голосовать за часть «исправить» (вы можете в конечном итоге реализовать stepобратный вызов), но часть «почему» довольно ясна.
John Dvorak
@Spudley: да, я знаю: целью поддержки IE9 будет использование setInterval и вызов функции DoRotate несколько раз.
frenchie 03
Кстати, я уже указывал на библиотеку CSS Sandpaper в своем ответе на ваш другой вопрос, который является полифилом для переходов CSS в IE. Возможно, вы захотите попробовать.
Spudley 03

Ответы:

222

CSS-преобразования пока нельзя анимировать с помощью jQuery. Вы можете сделать что-то вроде этого:

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

Вы можете узнать больше о пошаговом обратном вызове здесь: http://api.jquery.com/animate/#step

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

И, кстати: вам не нужно префикс преобразований css3 с помощью jQuery 1.7+

Обновить

Вы можете обернуть это в jQuery-плагин, чтобы немного облегчить себе жизнь:

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

Обновление2

Я оптимизировал его немного , чтобы сделать заказ easing, durationи completeнезначительным.

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

Обновление 2.1

Спасибо matteo, который заметил проблему с this-контекстом в полной- callback. Если это исправлено путем привязки обратного вызова jQuery.proxyк каждому узлу.

Я добавил редакцию в код раньше, начиная с обновления 2 .

Обновление 2.2

Это возможная модификация, если вы хотите сделать что-то вроде переключения вращения вперед и назад. Я просто добавил к функции параметр запуска и заменил эту строку:

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

Если кто-то знает, как сделать это более универсальным для всех случаев использования, независимо от того, хотят ли они устанавливать начальную степень, внесите соответствующие изменения.


Использование ... довольно просто!

В основном у вас есть два способа достичь желаемого результата. Но сначала давайте посмотрим на аргументы:

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

За исключением "угла", все они необязательны и возвращаются к свойствам по умолчанию jQuery.fn.animate:

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

1-й

Это самый короткий способ, но он выглядит немного непонятным, чем больше аргументов мы передаем.

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

2-й

Я предпочитаю использовать объекты, если аргументов больше трех, поэтому мне больше всего нравится этот синтаксис:

$(node).animateRotate(90, {
  duration: 1337,
  easing: 'linear',
  complete: function () {},
  step: function () {}
});
yckart
источник
4
Можете ли вы вставить это в скрипку?
frenchie 03
4
Хорошо, очень здорово: это плагин для кроссбраузерного (IE9 +) вращения CSS3 !! Вы можете утверждать это: вы построили это. Хорошая работа!
frenchie 04
1
@matteo Приносим извинения за поздний ответ и благодарим за тест. Мне нужно было немного времени, чтобы решить проблему, но я понял! fiddle.jshell.net/P5J4V/43 Кстати, я упомянул ваше расследование в своем ответе :)
yckart
1
@matteo Причина, по thisкоторой не упоминается объект DOM, состоит в том, что контекст установлен на объект, который animate()был вызван, в данном случае {deg: 0}установлен на контекст. Вы можете исправить это, изменив контекст каждой функции обратного вызова с помощью apply()/ call()или $.proxy()(как показал @yckart). Вот мое решение, чтобы исправить ВСЕ обратные вызовы и разрешить трехмерное вращение: jsfiddle.net/TrevinAvery/P5J4V/44
Тревин Эйвери
1
Если вы хотите анимировать один и тот же элемент снова и снова, 0каждый раз начиная с градусов не приведет к ожидаемому поведению, поэтому вам необходимо инициализировать с текущим значением поворота. Как это сделать, объясняется здесь: stackoverflow.com/a/11840120/61818
Асбьёрн Ульсберг
17

Спасибо yckart! Большой вклад. Я немного доработал ваш плагин. Добавлен startAngle для полного контроля и кроссбраузерности 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
        });
    });
};
однообразное имя
источник
5
jQuery автоматически добавляет необходимый префикс поставщика, поэтому в этом нет необходимости!
yckart
+1 за кросс-платформу. Отлично. @yckart: автоматический префикс у меня в этом случае не работает.
lsmpascal
@PaxMaximinus Какую jQuery-версию вы используете? blog.jquery.com/2012/08/09/jquery-1-8-released
yckart
@yckart: версия 1.7.1.
lsmpascal
1
@PaxMaximinus Как вы можете видеть в статье из jquery-blog, автопрефикс только что появился jquery-1.8+!
yckart
10

jQuery transit , вероятно, облегчит вашу жизнь, если вы имеете дело с анимацией CSS3 через jQuery.

ИЗМЕНИТЬ март 2014 г. (потому что мой совет постоянно голосовал вверх и вниз с тех пор, как я его опубликовал)

Позвольте мне объяснить, почему я изначально намекал на плагин выше:

Обновление DOMна каждом этапе (т.е. $.animate) не идеально с точки зрения производительности. Это работает, но, скорее всего, будет медленнее, чем чистые переходы CSS3 или анимация CSS3 .

Это происходит главным образом потому, что браузер получает возможность заранее подумать, если вы укажете, как будет выглядеть переход от начала до конца.

Для этого вы можете, например, создать класс CSS для каждого состояния перехода и использовать jQuery только для переключения состояния анимации.

Как правило, это довольно удобно, поскольку вы можете настраивать анимацию вместе с остальной частью вашего CSS, вместо того, чтобы смешивать ее со своей бизнес-логикой:

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

Если какой-либо из параметров преобразования является динамическим, вы, конечно, можете использовать вместо него атрибут style:

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

Более подробная информация о переходах CSS3 на MDN .

ОДНАКО есть еще несколько вещей, о которых следует помнить, и все это может стать немного сложным, если у вас есть сложные анимации, цепочки и т. Д., А jQuery Transit просто выполняет все хитрые биты под капотом:

$('.eye').transit({ rotate: '90deg'}); // easy huh ?
Theo.T
источник
3

Чтобы сделать этот кроссбраузер, включая IE7 +, вам нужно будет расширить плагин матрицей преобразования. Поскольку префикс поставщика выполняется в jQuery из jquery-1.8 +, я оставлю это для 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);
        }
    });
};

Примечание. Параметры optionsи startAngleявляются необязательными, если вам нужно установить только startAngleuse {}или nullfor options.

Пример использования:

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

См. Также этот jsfiddle для демонстрации.

Обновление : теперь вы также можете передать extra: {}параметры. Это позволит вам одновременно выполнять другие анимации. Например:

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

Это повернет элемент на 90 градусов, переместит его вправо на 100 пикселей и сделает его полупрозрачным одновременно во время анимации.

Йети
источник
Или IE9, в Firefox, но только firefox.
Лиам
Хорошо, теперь он работает в Chrome, Firefox и IE10. Ты можешь протестировать IE9, Лиам? Проблема заключалась в том, что свойство преобразования не было определено для Chrome и IE, поэтому сценарий считал, что свойство преобразования недоступно. Таким образом, я изменил сценарий , чтобы включить все префиксы: ms, o, webkit, mozчтобы правильно обеспечить обнаружение. Скрипка также обновлена ​​до версии 12.
Yeti
2

это мое решение:

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

тогда вы можете использовать его в анимированном 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' });
AntiCampeR
источник
1

Другой ответ, потому что jQuery.transit несовместим с jQuery.easing. Это решение поставляется как расширение jQuery. В более общем случае вращение - это частный случай:

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

Использование очень просто:

$(element).animateStep({from: 0, to: 90, step: $.fn.rotate});
Шины
источник
0

Без плагина кроссбраузер с 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);
Алексей Алексеенко
источник