web-dev-qa-db-fra.com

Afficher le fichier GIF de spinner lors d’une demande $ http dans AngularJS?

J'utilise le service $http d'AngularJS pour effectuer une demande Ajax.

Comment un spinner GIF (ou un autre type d'indicateur occupé) peut-il être affiché pendant l'exécution de la demande Ajax?

Je ne vois rien comme un ajaxstartevent dans la documentation d'AngularJS.

232
Ajay Beniwal

Voici les courant dernières incantations AngularJS:

angular.module('SharedServices', [])
    .config(function ($httpProvider) {
        $httpProvider.responseInterceptors.Push('myHttpInterceptor');
        var spinnerFunction = function (data, headersGetter) {
            // todo start the spinner here
            //alert('start spinner');
            $('#mydiv').show();
            return data;
        };
        $httpProvider.defaults.transformRequest.Push(spinnerFunction);
    })
// register the interceptor as a service, intercepts ALL angular ajax http calls
    .factory('myHttpInterceptor', function ($q, $window) {
        return function (promise) {
            return promise.then(function (response) {
                // do something on success
                // todo hide the spinner
                //alert('stop spinner');
                $('#mydiv').hide();
                return response;

            }, function (response) {
                // do something on error
                // todo hide the spinner
                //alert('stop spinner');
                $('#mydiv').hide();
                return $q.reject(response);
            });
        };
    });

//regular angular initialization continued below....
angular.module('myApp', [ 'myApp.directives', 'SharedServices']).
//.......

Voici le reste (HTML/CSS) .... using

$('#mydiv').show(); 
$('#mydiv').hide(); 

pour le basculer. NOTE: ce qui précède est utilisé dans le module angular au début de l'article

#mydiv {  
    position:absolute;
    top:0;
    left:0;
    width:100%;
    height:100%;
    z-index:1000;
    background-color:grey;
    opacity: .8;
 }

.ajax-loader {
    position: absolute;
    left: 50%;
    top: 50%;
    margin-left: -32px; /* -1 * image width / 2 */
    margin-top: -32px;  /* -1 * image height / 2 */
    display: block;     
}

<div id="mydiv">
    <img src="lib/jQuery/images/ajax-loader.gif" class="ajax-loader"/>
</div>
87
bulltorious

Cela dépend vraiment de votre cas d'utilisation spécifique, mais un moyen simple suivrait un modèle comme celui-ci:

.controller('MainCtrl', function ( $scope, myService ) {
  $scope.loading = true;
  myService.get().then( function ( response ) {
    $scope.items = response.data;
  }, function ( response ) {
    // TODO: handle the error somehow
  }).finally(function() {
    // called no matter success or failure
    $scope.loading = false;
  });
});

Et réagissez ensuite dans votre modèle:

<div class="spinner" ng-show="loading"></div>
<div ng-repeat="item in items>{{item.name}}</div>
471
Josh David Miller

Voici une version utilisant un directive et ng-hide.

Cela montrera le chargeur pendant tous les appels via le service $http de angular.

Dans le modèle:

<div class="loader" data-loading></div>

directive:

angular.module('app')
  .directive('loading', ['$http', function ($http) {
    return {
      restrict: 'A',
      link: function (scope, element, attrs) {
        scope.isLoading = function () {
          return $http.pendingRequests.length > 0;
        };
        scope.$watch(scope.isLoading, function (value) {
          if (value) {
            element.removeClass('ng-hide');
          } else {
            element.addClass('ng-hide');
          }
        });
      }
    };
}]);

en utilisant la classe ng-hide sur l'élément, vous pouvez éviter jquery.


Personnaliser: ajoutez une interceptor

Si vous créez un intercepteur de chargement, vous pouvez afficher/masquer le chargeur en fonction d'une condition.

directive:

var loadingDirective = function ($rootScope) {
  return function ($scope, element, attrs) {
      $scope.$on("loader_show", function () {
          return element.removeClass('ng-hide');
      });
      return $scope.$on("loader_hide", function () {
          return element.addClass('ng-hide');
      });
  };
};

intercepteur:

  • par exemple: ne montre pas spinner quand response.background === true;
  • Intercepter request et/ou response pour régler $rootScope.$broadcast("loader_show"); ou $rootScope.$broadcast("loader_hide");

plus d'infos sur l'écriture d'un intercepteur

44
punkrockpolly

Si vous utilisez ngResource, l'attribut $ resolu d'un objet est utile pour les chargeurs:

Pour une ressource comme suit:

var User = $resource('/user/:id', {id:'@id'});
var user = User.get({id: 1})

Vous pouvez lier un chargeur à l'attribut $ résolu de l'objet ressource:

<div ng-hide="user.$resolved">Loading ...</div>
31
Alexander Sweeney

https://github.com/wongatech/angular-http-loader est un bon projet pour cela.

Exemple ici http://wongatech.github.io/angular-http-loader/

Le code ci-dessous montre un exemple de modèle/loader.tpl.html lorsqu'une demande est en cours d'exécution.

<div ng-http-loader template="example/loader.tpl.html"></div>
14
eddiec

Je viens de découvrir la directive angular-busy qui montre un petit chargeur en fonction d’un appel asynchrone.

Par exemple, si vous devez créer un GET, référencez la promesse dans votre $scope,

$scope.req = $http.get('http://google.fr');

et appelez comme ça:

<div cg-busy="req"></div>

Voici la GitHub.

Vous pouvez également l'installer avec bower (n'oubliez pas de mettre à jour les dépendances de votre projet):

bower install angular-busy --save
13
Balthazar

Si vous enregistrez vos appels d'API dans un service/une usine, vous pouvez suivre le compteur de chargement à cet endroit (par réponse et une excellente suggestion simultanée de @JMaylin), et référencer le compteur de chargement via une directive. Ou toute combinaison de ceux-ci.

API WRAPPER

yourModule
    .factory('yourApi', ['$http', function ($http) {
        var api = {}

        //#region ------------ spinner -------------

        // ajax loading counter
        api._loading = 0;

        /**
         * Toggle check
         */
        api.isOn = function () { return api._loading > 0; }

        /**
         * Based on a configuration setting to ignore the loading spinner, update the loading counter
         * (for multiple ajax calls at one time)
         */
        api.spinner = function(delta, config) {
            // if we haven't been told to ignore the spinner, change the loading counter
            // so we can show/hide the spinner

            if (NG.isUndefined(config.spin) || config.spin) api._loading += delta;

            // don't let runaway triggers break stuff...
            if (api._loading < 0) api._loading = 0;

            console.log('spinner:', api._loading, delta);
        }
        /**
         * Track an ajax load begin, if not specifically disallowed by request configuration
         */
        api.loadBegin = function(config) {
            api.spinner(1, config);
        }
        /**
         * Track an ajax load end, if not specifically disallowed by request configuration
         */
        api.loadEnd = function (config) {
            api.spinner(-1, config);
        }

        //#endregion ------------ spinner -------------

        var baseConfig = {
            method: 'post'
            // don't need to declare `spin` here
        }

        /**
         * $http wrapper to standardize all api calls
         * @param args stuff sent to request
         * @param config $http configuration, such as url, methods, etc
         */
        var callWrapper = function(args, config) {
            var p = angular.extend(baseConfig, config); // override defaults

            // fix for 'get' vs 'post' param attachment
            if (!angular.isUndefined(args)) p[p.method == 'get' ? 'params' : 'data'] = args;

            // trigger the spinner
            api.loadBegin(p);

            // make the call, and turn of the spinner on completion
            // note: may want to use `then`/`catch` instead since `finally` has delayed completion if down-chain returns more promises
            return $http(p)['finally'](function(response) {
                api.loadEnd(response.config);
                return response;
            });
        }

        api.DoSomething = function(args) {
            // yes spinner
            return callWrapper(args, { cache: true });
        }
        api.DoSomethingInBackground = function(args) {
            // no spinner
            return callWrapper(args, { cache: true, spin: false });
        }

        // expose
        return api;
    });

DIRECTIVE DE SPINNER

(function (NG) {
    var loaderTemplate = '<div class="ui active dimmer" data-ng-show="hasSpinner()"><div class="ui large loader"></div></div>';

    /**
     * Show/Hide spinner with ajax
     */
    function spinnerDirective($compile, api) {
        return {
            restrict: 'EA',
            link: function (scope, element) {
                // listen for api trigger
                scope.hasSpinner = api.isOn;

                // attach spinner html
                var spin = NG.element(loaderTemplate);
                $compile(spin)(scope); // bind+parse
                element.append(spin);
            }
        }
    }

    NG.module('yourModule')
        .directive('yourApiSpinner', ['$compile', 'yourApi', spinnerDirective]);
})(angular);

USAGE

<div ng-controller="myCtrl" your-api-spinner> ... </div>
5
drzaus

Pour les chargements de page et les modaux, le moyen le plus simple consiste à utiliser la directive ng-show et à utiliser l'une des variables de données de portée. Quelque chose comme:

ng-show="angular.isUndefined(scope.data.someObject)".

Ici, bien que someObject ne soit pas défini, le compteur s’affiche. Une fois que le service est revenu avec les données et que someObject est rempli, le compteur retourne à son état masqué.

5
Arnold.Krumins

C'est le moyen le plus simple d'ajouter un disque, je suppose: -

Vous pouvez utiliser ng-show avec la balise div de n’importe laquelle de ces belles fileuses http://tobiasahlin.com/spinkit/ {{Ce n’est pas ma page}}

et alors vous pouvez utiliser ce genre de logique

//ajax start
    $scope.finderloader=true;
    
          $http({
    method :"POST",
    url : "your URL",
  data: { //your data
     
     }
  }).then(function mySucces(response) {
    $scope.finderloader=false;
      $scope.search=false;          
    $scope.myData =response.data.records;
  });
     
    //ajax end 
    
<div ng-show="finderloader" class=spinner></div>
//add this in your HTML at right place
3
ashish malgawa
Based on Josh David Miller response:

  <body>
  <header>
  </header>
<div class="spinner" ng-show="loading">
  <div class="loader" ></div>
</div>

<div ng-view=""></div>

<footer>
</footer>

</body>

Ajouter ce css:

    .loader {
  border: 16px solid #f3f3f3;
  border-radius: 50%;
  border-top: 16px solid #3498db;
  border-bottom : 16px solid black;
  width: 80px;
  height: 80px;
  -webkit-animation: spin 2s linear infinite;
  animation: spin 2s linear infinite;
  position: absolute;
  top: 45%;
  left: 45%;
}

@-webkit-keyframes spin {
  0% { -webkit-transform: rotate(0deg); }
  100% { -webkit-transform: rotate(360deg); }
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}


.spinner{
  width: 100%;
height: 100%;
z-index: 10000;
position: absolute;
top: 0;
left: 0;
margin: 0 auto;
text-align: center;
vertical-align: middle;
background: white;
opacity: 0.6;
}

Et juste dans votre angular ajoutez:

$ rootScope.loading = false; $ rootScope.loading = true; -> quand $ http.get se termine.

2
cabaji99

Partage de ma version de l'excellente réponse de @bulltorious, mise à jour pour les nouvelles constructions angular (j'ai utilisé la version 1.5.8 avec ce code), et a également intégré l'idée de @ JMaylin d'utiliser un compteur pour être robuste plusieurs requêtes simultanées et l'option permettant de ne pas afficher l'animation pour les demandes prenant moins d'un minimum de millisecondes:

var app = angular.module('myApp');
var BUSY_DELAY = 1000; // Will not show loading graphic until 1000ms have passed and we are still waiting for responses.

app.config(function ($httpProvider) {
  $httpProvider.interceptors.Push('busyHttpInterceptor');
})
  .factory('busyHttpInterceptor', ['$q', '$timeout', function ($q, $timeout) {
    var counter = 0;
    return {
      request: function (config) {
        counter += 1;
        $timeout(
          function () {
            if (counter !== 0) {
              angular.element('#busy-overlay').show();
            }
          },
          BUSY_DELAY);
        return config;
      },
      response: function (response) {
        counter -= 1;
        if (counter === 0) {
          angular.element('#busy-overlay').hide();
        }
        return response;
      },
      requestError: function (rejection) {
        counter -= 1;
        if (counter === 0) {
          angular.element('#busy-overlay').hide();
        }
        return rejection;
      },
      responseError: function (rejection) {
        counter -= 1;
        if (counter === 0) {
          angular.element('#busy-overlay').hide();
        }
        return rejection;
      }
    }
  }]);
2
qwwqwwq

Vous pouvez utiliser angular interceptor pour gérer les appels de demandes http

  <div class="loader">
    <div id="loader"></div>
  </div>

<script>
    var app = angular.module("myApp", []);

    app.factory('httpRequestInterceptor', ['$rootScope', '$location', function ($rootScope, $location) {
        return {
            request: function ($config) {
                $('.loader').show();
                return $config;
            },
            response: function ($config) {
                $('.loader').hide();
                return $config;
            },
            responseError: function (response) {
                return response;
            }
        };
    }]);

    app.config(['$stateProvider', '$urlRouterProvider', '$httpProvider',
        function ($stateProvider, $urlRouterProvider, $httpProvider) {
            $httpProvider.interceptors.Push('httpRequestInterceptor');
        }]);

</script>

https://stackoverflow.com/a/49632155/4976786

2
Om Sharma

Cela fonctionne bien pour moi:

HTML:

  <div id="loader" class="ng-hide" ng-show="req.$$state.pending">
    <img class="ajax-loader" 
         width="200" 
         height="200" 
         src="/images/spinner.gif" />
  </div>

Angulaire:

  $scope.req = $http.get("/admin/view/"+id).success(function(data) {          
      $scope.data = data;
  });

Bien que la promesse renvoyée par $ http soit en attente, ng-show l’évaluera comme étant "la vérité". Ceci est automatiquement mis à jour une fois la promesse résolue ... ce qui est exactement ce que nous voulons.

1
dank

Manière simple sans intercepteurs ni jQuery

C'est un moyen simple d'afficher un spinner qui ne nécessite pas de bibliothèque tierce, d'intercepteur ou de jQuery.

Dans le contrôleur, définissez et réinitialisez un indicateur.

function starting() {
    //ADD SPINNER
    vm.starting = true;
    $http.get(url)
      .then(function onSuccess(response) {
        vm.data = response.data;
    }).catch(function onReject(errorResponse) {
        console.log(errorResponse.status);
    }).finally(function() {
        //REMOVE SPINNER
        vm.starting = false;
    });
};

Dans le code HTML, utilisez le drapeau:

<div ng-show="vm.starting">
    <img ng-src="spinnerURL" />
</div>

<div ng-hide="vm.starting">
    <p>{{vm.data}}</p>
</div>

Le drapeau vm.starting est défini sur true au démarrage de la XHR et est effacé à la fin de celle-ci.

1
georgeawg

tilisé après intercepter pour afficher la barre de chargement sur la requête http

'use strict';
appServices.factory('authInterceptorService', ['$q', '$location', 'localStorage','$injector','$timeout', function ($q, $location, localStorage, $injector,$timeout) {

var authInterceptorServiceFactory = {};
var requestInitiated;

//start loading bar
var _startLoading = function () {
   console.log("error start loading");
   $injector.get("$ionicLoading").show();

}

//stop loading bar
var _stopLoading = function () {
    $injector.get("$ionicLoading").hide();
}

//request initiated
var _request = function (config) {
     requestInitiated = true;
    _startLoading();
    config.headers = config.headers || {};
    var authDataInitial = localStorage.get('authorizationData');
    if (authDataInitial && authDataInitial.length > 2) {
        var authData = JSON.parse(authDataInitial);
        if (authData) {
            config.headers.Authorization = 'Bearer ' + authData.token;
        }
    }
    return config;
}

//request responce error
var _responseError = function (rejection) {
   _stopLoading();
    if (rejection.status === 401) {
        $location.path('/login');
    }
    return $q.reject(rejection);
}

//request error
var _requestError = function (err) {
   _stopLoading();
   console.log('Request Error logging via interceptor');
   return err;
}

//request responce
var _response = function(response) {
    requestInitiated = false;

   // Show delay of 300ms so the popup will not appear for multiple http request
   $timeout(function() {

        if(requestInitiated) return;
        _stopLoading();
        console.log('Response received with interceptor');

    },300);

return response;
}



authInterceptorServiceFactory.request = _request;
authInterceptorServiceFactory.responseError = _responseError;
authInterceptorServiceFactory.requestError = _requestError;
authInterceptorServiceFactory.response = _response;

return authInterceptorServiceFactory;
}]);
1
Dinesh Vaitage

Une autre solution pour afficher le chargement entre différents changements d’URL est la suivante:

$rootScope.$on('$locationChangeStart', function() {
  $scope.loading++;
});

$rootScope.$on('$locationChangeSuccess', function() {
  $timeout(function() {
    $scope.loading--;
  }, 300);
});

Et ensuite, dans le balisage, il suffit de basculer la roulette avec ng-show="loading".

Si vous souhaitez l'afficher sur les requêtes ajax, ajoutez simplement $scope.loading++ au début et à la fin de la requête, ajoutez $scope.loading--.

0
Chrillewoodz

La manière suivante prend note de toutes les demandes et ne cache que lorsque toutes les demandes sont terminées:

app.factory('httpRequestInterceptor', function(LoadingService, requestCount) {
    return {
        request: function(config) {
            if (!config.headers.disableLoading) {
                requestCount.increase();
                LoadingService.show();
            }
            return config;
        }
    };
}).factory('httpResponseInterceptor', function(LoadingService, $timeout, error, $q, requestCount) {
    function waitAndHide() {
        $timeout(function() {
            if (requestCount.get() === 0){
                LoadingService.hide();
            }
            else{
                waitAndHide();
            }
        }, 300);
    }

    return {
        response: function(config) {
            requestCount.descrease();
            if (requestCount.get() === 0) {
                waitAndHide();
            }
            return config;
        },
        responseError: function(config) {
            requestCount.descrease();
            if (requestCount.get() === 0) {
                waitAndHide();
            }
            var deferred = $q.defer();
            error.show(config.data, function() {
                deferred.reject(config);
            });
            return deferred.promise;
        }
    };
}).factory('requestCount', function() {
    var count = 0;
    return {
        increase: function() {
            count++;
        },
        descrease: function() {
            if (count === 0) return;
            count--;
        },
        get: function() {
            return count;
        }
    };
})
0
Yerken

Vous pouvez essayer quelque chose comme ça aussi:

Créer une directive:

myApp.directive('loader', function () {
    return {
        restrict: 'A',
        scope: {cond: '=loader'},
        template: '<span ng-if="isLoading()" class="soft"><span class="fa fa-refresh fa-spin"></span></span>',
        link: function (scope) {
            scope.isLoading = function() {
                var ret = scope.cond === true || (
                        scope.cond &&
                        scope.cond.$$state &&
                        angular.isDefined(scope.cond.$$state.status) &&
                        scope.cond.$$state.status === 0
                    );
                return ret;
            }
        }
    };
}); 

Ensuite, vous ajoutez quelque chose comme ceci à mainCtrl

    // Return TRUE if some request is LOADING, else return FALSE
    $scope.isLoading = function() {
        return $http.pendingRequests.length > 0;
    };

Et HTML peut ressembler à ceci:

<div class="buttons loader">
    <span class="icon" loader="isLoading()"></span>
</div>
0
Andurit

créer une directive avec ce code:

$scope.$watch($http.pendingRequests, toggleLoader);

function toggleLoader(status){
  if(status.length){
    element.addClass('active');
  } else {
    element.removeClass('active');
  }
}
0
Oleg Ozimok

si vous voulez afficher le chargeur pour chaque appel de requête http, vous pouvez utiliser l'intercepteur angular pour gérer les appels de requête http,

voici un exemple de code

<body data-ng-app="myApp">
<div class="loader">
    <div id="loader"></div>
</div>

<script>
    var app = angular.module("myApp", []);

    app.factory('httpRequestInterceptor', ['$rootScope', '$location', function ($rootScope, $location) {
        return {
            request: function ($config) {
                $('.loader').show();
                return $config;
            },
            response: function ($config) {
                $('.loader').hide();
                return $config;
            },
            responseError: function (response) {
                return response;
            }
        };
    }]);

    app.config(['$stateProvider', '$urlRouterProvider', '$httpProvider',
        function ($stateProvider, $urlRouterProvider, $httpProvider) {
            $httpProvider.interceptors.Push('httpRequestInterceptor');
        }]);

</script>
</body>
0
Om Sharma

Depuis que la fonctionnalité deposition: fixeda changé récemment, j'avais du mal à montrer le gif loader au-dessus de tous les éléments, je devais donc utiliser angular's incorporéjQuery.

Html

<div ng-controller="FetchController">
      <div id="spinner"></div>
</div>

Css

#spinner {display: none}
body.spinnerOn #spinner { /* body tag not necessary actually */
   display: block;
   height: 100%;
   width: 100%;
   background: rgba(207, 13, 48, 0.72) url(img/loader.gif) center center no-repeat;
   position: fixed;
   top: 0;
   left: 0;
   z-index: 9999;
}
body.spinnerOn main.content { position: static;} /* and whatever content needs to be moved below your fixed loader div */

Controller

app.controller('FetchController', ['$scope', '$http', '$templateCache', '$location', '$q',
function($scope, $http, $templateCache, $location, $q) {

angular.element('body').addClass('spinnerOn'); // add Class to body to show spinner

$http.post( // or .get(
    // your data here
})
.then(function (response) {
    console.info('success');     
    angular.element('body').removeClass('spinnerOn'); // hide spinner

    return response.data;               
}, function (response) {                   
    console.info('error'); 
    angular.element('body').removeClass('spinnerOn'); // hide spinner
});

})

J'espère que cela t'aides :)

0
Magor Menessy

Toutes les réponses sont soit compliquées, soit qu'il soit nécessaire de définir des variables pour chaque requête, ce qui est une pratique très fausse si nous connaissons le concept DRY. Ici, par exemple, un intercepteur simple, je mets la souris sur wait quand ajax commence et le règle sur auto quand ajax se termine.

$httpProvider.interceptors.Push(function($document) {
    return {
     'request': function(config) {
         // here ajax start
         // here we can for example add some class or show somethin
         $document.find("body").css("cursor","wait");

         return config;
      },

      'response': function(response) {
         // here ajax ends
         //here we should remove classes added on request start

         $document.find("body").css("cursor","auto");

         return response;
      }
    };
  });

Le code doit être ajouté dans l'application config app.config. J'ai montré comment changer la souris lors du chargement, mais il est possible d'afficher ou de masquer le contenu du chargeur, ou d'ajouter, de supprimer certaines classes CSS qui affichent le chargeur.

Interceptor s'exécutera à chaque appel ajax, il n'est donc pas nécessaire de créer des variables booléennes spéciales ($ scope.loading = true/false, etc.) à chaque appel http.

0
Maciej Sikora

Voici mon implémentation, aussi simple qu'un ng-show et un compteur de requêtes.

Il utilise un nouveau service pour toutes les demandes adressées à $ http:

myApp.service('RqstSrv', [ '$http', '$rootScope', function($http, $rootScope) {
    var rqstService = {};

    rqstService.call = function(conf) {

        $rootScope.currentCalls = !isNaN($rootScope.currentCalls) ?  $rootScope.currentCalls++ : 0;

        $http(conf).then(function APICallSucceed(response) {
            // Handle success
        }, function APICallError(response) {
            // Handle error
        }).then(function() {
            $rootScope.currentCalls--;
        });
    }
} ]);

Et puis vous pouvez utiliser votre base de chargeur sur le nombre d'appels en cours:

<img data-ng-show="currentCalls > 0" src="images/ajax-loader.gif"/>
0
.factory('authHttpResponseInterceptor', ['$q', function ($q) {
        return {
            request: function(config) {
                angular.element('#spinner').show();
                return config;
            },
            response : function(response) {
                angular.element('#spinner').fadeOut(3000);
                return response || $q.when(response);
            },
            responseError: function(reason) {
                angular.element('#spinner').fadeOut(3000);
                return $q.reject(reason);
            }
        };
    }]);



 .config(['$routeProvider', '$locationProvider', '$translateProvider', '$httpProvider',
            function ($routeProvider, $locationProvider, $translateProvider, $httpProvider) {
                $httpProvider.interceptors.Push('authHttpResponseInterceptor');
    }
]);

in your Template
<div id="spinner"></div>


css   

#spinner,
#spinner:after {
  border-radius: 50%;
  width: 10em;
  height: 10em;
  background-color: #A9A9A9;
  z-index: 10000;
  position: absolute;
  left: 50%;
  bottom: 100px;
}
@-webkit-keyframes load8 {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
@keyframes load8 {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
0
user1853287