web-dev-qa-db-fra.com

AngularJS: Le meilleur moyen de regarder les dimensions?

J'ai donc proposé quelques solutions à ce problème et je ne suis toujours pas certain de ce qui convient le mieux. Pour commencer, il y a une question similaire que j'ai pu trouver, bien qu'elle soit un peu vieille. Pour ceux qui liront ceci plus tard: Regarder les changements de dimension dans Angular

L'objectif

J'ai des parties de mon application dans lesquelles des éléments de hauteur réactifs sont nécessaires. Je veux le moyen le plus rapide et le plus attrayant visuellement de le faire au niveau de la couche interface utilisateur/directive sans avoir besoin d’événements de changement explicitement diffusés.

Première option: directive

Une directive simple peut enregistrer les dimensions sur chaque boucle de résumé (et événement de redimensionnement)

return {
  scope: {
    size: '=ngSize'
  },
  link: function($scope, element, attrs) {

    var windowEl = angular.element($window),
        handler  = function() {
          if ($scope.size &&
              element.outerWidth() === $scope.size.width &&
              element.outerHeight() === $scope.size.height) return false;
          $scope.size = {
            width: element.outerWidth(),
            height: element.outerHeight()
          };
        };

    windowEl.on('resize', function() {
      $scope.$apply(handler);
    });

    $root.$watch(function() { return [element.outerWidth(), element.outerHeight()] }, handler, true);

  }
};

Problème: Le changement ne se propage pas assez vite et un retard visuel est perceptible.

Solution: Utilisation d'un appel d'intervalle


Deuxième option: $ intervalle

J'ai essayé la même chose avec un appel $ interval et cela a fonctionné, mais l'utilisation du processeur était étonnamment élevée, même après avoir inversé le contrôle et gardé la trace des éléments d'une simple collection racine surveillée par valeur (en évitant les minuteurs simultanés produits par plusieurs instances de la directive ).

Mis à part un problème lié au GC dans mon environnement (je ne vois rien qui suggère cela actuellement), pourrait-il y avoir une meilleure approche pour créer ce type de présentation fluide?

Solution proposée/question

Ma première pensée serait un moyen de créer une boucle de type $ digest concurrente, afin de surveiller efficacement le DOM dans son ensemble pour détecter tout changement visuel. Est-il possible de parcourir efficacement tous les styles calculés, par exemple, pour produire un hachage bon marché qui peut être surveillé par valeur? Quelque chose qui peut être déclenché relativement à moindre coût chaque fois qu'un style calculé pertinent est ajouté et/ou modifié?

Avant de le construire et de le profiler, est-ce que quelqu'un pourrait dire s'il est même réaliste ou s'il est simplement plus logique de résumer/de refactoriser les déclencheurs de redimensionnement?

Toute autre idée sur la meilleure façon d’atteindre cet objectif dans 1.2.9 ?

[edit] Une autre question, peut-être plus simple: est-il même possible de fournir un taux de rafraîchissement réaliste de 1-200 ms via Javascript de manière efficace du point de vue informatique? Si tel est le cas, serait-ce ainsi l'intervalle $ angulaire, ou une implémentation de «VanillaJS» serait-elle plus efficace?

22
Julian

Malheureusement, cette question n'a pas attiré autant d'attention que je l'aurais souhaité ... Et je n'ai pas le temps d'écrire une explication détaillée avec l'arrière-plan, des captures d'écran du profileur, etc. Mais j'ai trouvé une solution, et j'espère que cela aidera quelqu'un d'autre traitant du même problème.


Conclusion

La boucle $ digest dans toute application réaliste de moyenne à grande taille ne pourra pas gérer un rafraîchissement de 100 ms.

Étant donné l'exigence de setInterval, j'ai simplement contourné entièrement la boucle, optant plutôt pour ne diffuser un changement d'état que lorsque des dimensions différentes sont détectées (à l'aide des propriétés natives offsetWidth/Height).

L'exécution à un intervalle de 100 ms avec un seul manifeste d'étendue $root donne les meilleurs résultats de tout ce que j'ai testé, avec environ 10,2% de CPU à onglet actif sur mon MBP 2.4Ghz i7 2013 - acceptable par rapport à ~ 84% avec $interval.

Tous les commentaires et critiques sont les bienvenus, sinon, voici la directive autonome! Évidemment, vous pouvez faire preuve de créativité avec la portée et/ou les attributs pour personnaliser les observateurs, mais dans l’intérêt de rester sur le sujet, j’ai essayé d’omettre tout code superflu.

Il surveillera et liera les changements de taille d'un élément à une propriété de portée de votre choix, pour N éléments de complexité linéaire. Je ne peux pas garantir que ce soit le moyen le plus rapide/le meilleur, mais c'est l'implémentation la plus rapide que je puisse développer rapidement, qui permet de suivre les modifications de dimensions au niveau DOM indépendantes de tout état:

app.directive('ngSize', ['$rootScope', function($root) {
  return {
    scope: {
        size: '=ngSize'
    },
    link: function($scope, element, attrs) {

        $root.ngSizeDimensions  = (angular.isArray($root.ngSizeDimensions)) ? $root.ngSizeDimensions : [];
        $root.ngSizeWatch       = (angular.isArray($root.ngSizeWatch)) ? $root.ngSizeWatch : [];

        var handler = function() {
            angular.forEach($root.ngSizeWatch, function(el, i) {
                // Dimensions Not Equal?
                if ($root.ngSizeDimensions[i][0] != el.offsetWidth || $root.ngSizeDimensions[i][1] != el.offsetHeight) {
                    // Update Them
                    $root.ngSizeDimensions[i] = [el.offsetWidth, el.offsetHeight];
                    // Update Scope?
                    $root.$broadcast('size::changed', i);
                }
            });
        };

        // Add Element to Chain?
        var exists = false;
        angular.forEach($root.ngSizeWatch, function(el, i) { if (el === element[0]) exists = i });

        // Ok.
        if (exists === false) {
            $root.ngSizeWatch.Push(element[0]);
            $root.ngSizeDimensions.Push([element[0].offsetWidth, element[0].offsetHeight]);
            exists = $root.ngSizeWatch.length-1;
        }

        // Update Scope?
        $scope.$on('size::changed', function(event, i) {
            // Relevant to the element attached to *this* directive
            if (i === exists) {
                $scope.size = {
                    width: $root.ngSizeDimensions[i][0],
                    height: $root.ngSizeDimensions[i][1]
                };
            }
        });

        // Refresh: 100ms
        if (!window.ngSizeHandler) window.ngSizeHandler = setInterval(handler, 100);

        // Window Resize?
        // angular.element(window).on('resize', handler);

    }
  };
}]);
10
Julian

Découvrez http://snapjay.github.io/angularjs-breakpoint/

ce qui fait beaucoup de ce dont vous avez besoin, je pense. Vous pouvez regarder la largeur de la fenêtre et ainsi de suite. Je ne sais pas si cela fonctionne aussi en hauteur, mais vous pouvez l'étendre.

0
pfooti