2013-02-14 1 views
56

Когда пользователь прокручивает окно своего браузера ниже определенной точки, я перехожу к классу #page div.Связать класс с переключением на событие прокрутки окна

То, что я сделал до сих пор работает отлично:

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

(когда они прокручивать их окна под заголовком, 100px, класс переключен)

Хотя, поправьте меня если я ошибаюсь, я считаю, что это не правильный способ сделать это с помощью Angular.

Вместо этого я предположил, что лучшим способом для этого было бы использование ng-класса и сохранение булевского значения в области. Что-то вроде этого:

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

Хотя это не является динамическим, если изменить значение scope.boolChangeClass в скроллинга обратного вызова, то нг-класс не обновляется.

Итак, мой вопрос: как лучше всего переключить класс #page, используя AngularJS, когда пользователь прокручивается ниже определенной точки?

+4

У меня была точно такая же проблема сегодня :) И я решил это точно так же (вторая версия)! Я также не понимаю, почему ng-класс не обновляется, но если вы перевариваете (просто добавьте $ scope. $ Apply() после изменения boolChangeClass), он работает. –

+0

Спасибо @Flek, что сработало отлично :) – StuR

+0

Я на самом деле просто смущен, почему нам нужно явно позвонить, чтобы вызвать переваривание, потому что мы не используем какую-либо стороннюю библиотеку, все является Угловой. Mhh ... Может быть, кто-то узнает :) –

ответ

84

Благодаря Flek за ответ на мой вопрос в своем комментарии:

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(); 
     }); 
    }; 
}); 
+0

Эта директива запускает только один раз для всех. Как мы можем иметь возможность иметь несколько элементов с их собственными «boolChangeClass»? –

+7

Я бы очень советовал ПРОТИВ 'scope. $ Apply()'! Это приводит к полной переоценке страницы в каждом событии прокрутки. В моем случае я изменяю изображение, когда верхнее меню становится фиксированным (в значительной степени, как делают Google +). Поэтому я вручную установил его из директивы через javascript: 'logoelem.css ('background-image', 'url (' + scope.imageLogo + ')');'. Когда я использовал область применения. $ Apply() он пересчитал каждую переменную стиля '{{showMessage()}} на странице. И я использовал таблицу, поэтому каждое событие прокрутки привело к 40 перерасчетам. Это было лагги. Обновление необходимого элемента только для многих циклов процессора для меня. – arcol

+5

, если состояние можно упростить до: scope.boolChangeClass = this.pageYOffset> = 100; –

-4

Директивы не являются «в угловом мире», как они говорят. Поэтому вы должны использовать заявку, чтобы вернуться к ней при смене вещей.

+20

Кому-нибудь, кто приходит к этому вопросу, читая этот ответ и задаваясь вопросом; Не возражайте. Директивы, конечно, хорошо «внутри углового мира» и не нуждаются в $ apply в конце. Причина, по которой требуется применение, заключается в том, что обратный вызов связан с событием (в данном случае событием прокрутки). И события браузера, как правило, вне цикла углового события –

14

Это мое решение, это не так сложно и позволяет использовать его для нескольких разметки через простую директиву ng-класса. Таким образом, вы можете выбрать класс и scrollPos для каждого случая.

Ваш 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(); 
     }; 
    }); 

Ваш 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> 

работает JSFiddle here

EDIT:

Как $apply() на самом деле звонит $rootScope.$digest() вы можете прямо использовать $scope.$digest() вместо $scope.$apply() для Лучшая производительность в зависимости от контекста.
Краткая история: $apply() всегда будет работать, но сила $digest во всех областях, которые могут привести к проблеме .

+0

как я могу заставить его работать в моем случае. Я также сталкиваюсь с проблемой при получении события прокрутки, когда пользователь прокручивает div. У меня есть контроллер, где я хочу получить событие, когда пользователь прокручивает список. – User7723337

+0

Здесь я смотрю на позицию прокрутки объекта 'window'. Думаю, вам нужно следить за позиции прокрутки конкретного элемента. i.e: 'document.getElementById ('myDiv'). scrollTop;' – Freezystem

+0

Проблема заключается в том, что мой код внутри метода обратного вызова «$ window.onscroll = function()» вообще не вызван. Когда я делаю «$ window.onscroll = function() {console.log (« прокрутка »);}« тогда я не вижу «прокрутку» в журналах. – User7723337

2

Может быть, это может помочь :)

Controller

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

Html

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

Или

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

Direct ive

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

Спасибо за кучи за это! Работал для меня, но только после того, как я добавил атрибут id в div. –

19

Почему вы все предлагаете тяжелые операции? Я не понимаю, почему это не "Угловое" решение:

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

Таким образом, вы можете использовать его как это:

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

или

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

Это должен быть выбранный ответ. Прекрасно работает! Мне нравится функция многократного использования и позволяет нам определять класс и смещение. –

+1

Кажется, что это лучший ответ, в итоге я добавил дополнительную переменную, чтобы указать, к какому элементу вы хотите применить scrollClass. Также было изменено событие прокрутки для привязки к элементу, в котором включена директива, а не $ window. Сладкая директива, спасибо! –

+0

Отлично провел, и потребовалось 2 минуты, спасибо! Замена jquery.waypoints.js шаблона после маршрутизации SPA убила его. – RandomUs1r

0

насчет производительности?

  1. Всегда DEBOUNCE события сократить расчеты
  2. Использование scope.applyAsync уменьшить общий переваривать подсчет циклов
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. Если вам не нужно, чтобы вызвать наблюдателей/переваривает вообще затем использовать 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(); 
     } 
    }; 
    }); 

И вы можете использовать его как <header class-scroll="header--scrolled">