web-dev-qa-db-fra.com

Lier la classe bascule à l'événement de défilement de fenêtre

Lorsqu'un utilisateur fait défiler la fenêtre de son navigateur au-dessous d’un certain point, je change de classe.

Ce que j'ai fait jusqu'à présent fonctionne bien:

http://jsfiddle.net/eTTZj/29/

<div ng-app="myApp" scroll id="page">

    <header></header>
    <section></section>

</div>

app = angular.module('myApp', []);
app.directive("scroll", function ($window) {
    return function(scope, element, attrs) {
        angular.element($window).bind("scroll", function() {
             if (this.pageYOffset >= 100) {
                 element.addClass('min');
                 console.log('Scrolled below header.');
             } else {
                 element.removeClass('min');
                 console.log('Header is in view.');
             }
        });
    };
});

(quand ils font défiler leur fenêtre sous l'en-tête, 100px, la classe est basculée)

Bien que corrigez-moi si je me trompe, j'estime que ce n'est pas la bonne façon de faire cela avec Angular.

Au lieu de cela, j'ai supposé que la meilleure méthode pour ce faire serait d'utiliser ng-class et de stocker une valeur booléenne dans la portée. Quelque chose comme ça:

<div ng-app="myApp" scroll id="page" ng-class="{min: boolChangeClass}">

    <header></header>
    <section></section>

</div>

app = angular.module('myApp', []);
app.directive("scroll", function ($window) {
    return function(scope, element, attrs) {
        angular.element($window).bind("scroll", function() {
             if (this.pageYOffset >= 100) {
                 scope.boolChangeClass = true;
                 console.log('Scrolled below header.');
             } else {
                 scope.boolChangeClass = false;
                 console.log('Header is in view.');
             }
        });
    };
});

Bien que cela ne soit pas dynamique, si je modifie la valeur de scope.boolChangeClass dans le rappel de défilement, la classe ng ne se met pas à jour.

Ma question est donc la suivante: quel est le meilleur moyen de basculer la classe de #page en utilisant AngularJS lorsque l’utilisateur fait défiler en dessous d’un certain point?

59
StuR

Pourquoi suggérez-vous tous des opérations lourdes? Je ne vois pas pourquoi ce n'est pas une solution "angulaire":

.directive('changeClassOnScroll', function ($window) {
  return {
    restrict: 'A',
    scope: {
        offset: "@",
        scrollClass: "@"
    },
    link: function(scope, element) {
        angular.element($window).bind("scroll", function() {
            if (this.pageYOffset >= parseInt(scope.offset)) {
                element.addClass(scope.scrollClass);
            } else {
                element.removeClass(scope.scrollClass);
            }
        });
    }
  };
})

Donc, vous pouvez l'utiliser comme ceci:

<navbar change-class-on-scroll offset="500" scroll-class="you-have-scrolled-down"></navbar>

ou

<div change-class-on-scroll offset="500" scroll-class="you-have-scrolled-down"></div>
23
user1415066

Merci à Flek d'avoir répondu à ma question dans son commentaire:

http://jsfiddle.net/eTTZj/30/

<div ng-app="myApp" scroll id="page" ng-class="{min:boolChangeClass}">

    <header></header>
    <section></section>

</div>

app = angular.module('myApp', []);
app.directive("scroll", function ($window) {
    return function(scope, element, attrs) {
        angular.element($window).bind("scroll", function() {
             if (this.pageYOffset >= 100) {
                 scope.boolChangeClass = true;
             } else {
                 scope.boolChangeClass = false;
             }
            scope.$apply();
        });
    };
});
86
StuR

C’est ma solution, elle n’est pas si difficile et vous permet de l’utiliser pour plusieurs balises à travers une simple directive ng-class. Comme si vous pouviez choisir la classe et le scrollPos pour chaque cas.

Votre App.js:

angular.module('myApp',[])
    .controller('mainCtrl',function($window, $scope){
        $scope.scrollPos = 0;

        $window.onscroll = function(){
            $scope.scrollPos = document.body.scrollTop || document.documentElement.scrollTop || 0;
            $scope.$apply(); //or simply $scope.$digest();
        };
    });

Votre index.html:

<html ng-app="myApp">
    <head></head>
    <body>
        <section ng-controller="mainCtrl">
            <p class="red" ng-class="{fix:scrollPos >= 100}">fix me when scroll is equals to 100</p>
            <p class="blue" ng-class="{fix:scrollPos >= 150}">fix me when scroll is equals to 150</p>
        </section>
    </body>
</html>

travail JSFiddle ici

EDIT:

Comme $apply() appelle en réalité $rootScope.$digest(), vous pouvez directement utiliser $scope.$digest() au lieu de $scope.$apply() pour obtenir de meilleures performances en fonction du contexte.
Longue histoire courte: $apply() fonctionnera toujours, mais force le $digest Sur tous les scopes pouvant causer des problèmes de performance.

16
Freezystem

Peut-être que cela peut aider :)

Manette

$scope.scrollevent = function($e){
   // Your code
}

Html

<div scroll scroll-event="scrollevent">//scrollable content</div>

Ou

<body scroll scroll-event="scrollevent">//scrollable content</body>

Directif

.directive("scroll", function ($window) {
   return {
      scope: {
         scrollEvent: '&'
      },
      link : function(scope, element, attrs) {
        $("#"+attrs.id).scroll(function($e) { scope.scrollEvent != null ?  scope.scrollEvent()($e) : null })
      }
   }
})
2
Kelk

Qu'en est-il de la performance?

  1. Toujours annuler les événements pour réduire les calculs
  2. Utilisation scope.applyAsync pour réduire le nombre total de cycles de digestion
function debounce(func, wait) {
    var timeout;
    return function () {
        var context = this, args = arguments;
        var later = function () {
            timeout = null;
            func.apply(context, args);
        };

        if (!timeout) func.apply(context, args);
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
    };
}

angular.module('app.layout')
  .directive('classScroll', function ($window) {    
    return {
        restrict: 'A',
        link: function (scope, element) {    
            function toggle() {
                angular.element(element)
                  .toggleClass('class-scroll--scrolled', 
                    window.pageYOffset > 0);
                scope.$applyAsync();
            }    
            angular.element($window)
              .on('scroll', debounce(toggle, 50));

            toggle();
        }
    };
});

3. Si vous n'avez pas du tout besoin de déclencher des observateurs/digérés, utilisez compile

.directive('classScroll', function ($window, utils) {
    return {
        restrict: 'A',
        compile: function (element, attributes) {
            function toggle() {
                angular.element(element)
                  .toggleClass(attributes.classScroll,
                    window.pageYOffset > 0);
            }

            angular.element($window)
              .on('scroll', utils.debounce(toggle, 50));
            toggle();
        }
    };
  });

Et vous pouvez l'utiliser comme <header class-scroll="header--scrolled">

0
grigson