Выполнить функцию в контроллере после события прокрутки AngularJS

Скажем, у меня есть фид статей, которые складываются:

<div ng-repeat="article in articles">
  <h1> {{article.title}} </h1>
  <p> {{article.body}} </p>
</div>

При прокрутке каждой статьи:

<div scroll="atNewArticle(article)" ng-repeat="article in articles">
  ...
</div>

Я хочу выполнить функцию:

.controller('AppCtrl', ['$scope', function($scope) {
  $scope.atNewArticle = function(){
    console.log(article);
  }
}])

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

Вот что я пробовал до сих пор: http://jsfiddle.net/6VFJs/1/

Ответ 1

Есть несколько вещей, которые, вероятно, являются хорошей идеей (в зависимости от вашего конкретного варианта использования). Во-первых, несколько не-w500 > -специфических:

  • Используйте "debounced" версию прослушивателя для события прокрутки, чтобы функция выполнялась только после прекращения прокрутки пользователя. Для этого вы можете использовать lodash/underscore.

    var debounced = _.debounce(function() { .... }, 500); // Executes 500ms after last call of the debounced function.
    angular.element($document).on('scroll', debounced);
    
  • Используйте библиотеку для определения позиции/видимости любого элемента. verge.js - небольшая легкая библиотека, которую я нашел для этого. Один из способов использования этого:

    var visible = verge.inViewport(element);
    

    Но в зависимости от того, что именно вы хотите рассматривать как "видимое", вам может потребоваться изменить это.

  • Следите за тем, что видимо/не видно на каждом событии (так как у вас есть функция "atNewArticle", которая предполагает, что вы хотите, чтобы она вызывалась только при появлении элемента). Поэтому вам нужно создать массив видимых элементов и протестировать его на прокрутке.

    var index = visibleElements.indexOf(scope.scrollItem);
    

Angular - конкретная точка:

  • Передать 2 варианта директивы. 1) функция обратного вызова при появлении нового элемента и 2) сам элемент статьи, поэтому его можно вернуть обратно к обратному вызову.

    scope: {
         scroll: '&',
         scrollItem: '='
    }
    

    Они могут использоваться как:

    <div ng-repeat="article in articles" class="block" scroll="atNewArticle(item)" scroll-item="article">
    

    Это дает области директивы переменную scrollItem и функцию scroll. Поэтому директива может вызвать в соответствующей точке:

    scope.scroll({item:scope.scrollItem});
    

    Следует отметить синтаксис этого: это карта ключа-объекта.

Вы можете увидеть все это в действии на http://jsfiddle.net/Pb8t4/4/

Изменить: обновлена ​​ссылка на jsfiddle, которая включает вызов scope.$apply, чтобы любые изменения были правильно замечены Angular.

Ответ 2

Вы можете найти пример this.

Идея заключается в использовании: scroll binding с инструкцией if, которая фильтрует scope.$apply(attrs.scroll); вызов

Ответ 3

Вы должны привязать событие прокрутки к документу, поскольку документ является прокручиваемым элементом. Затем вы можете обнаружить scrollTop документа и сравнить со смещением повторяющихся элементов.

JSFiddle Demo: http://jsfiddle.net/daiweilu/nzBS5/2/

Я использую только jQuery и Angular 1.2.6. В основном я протестировал верхнюю границу элемента ng-repeat с верхней границей окна. Если элемент начинает покидать окно, функция на элементе ng-repeat выполняется один раз. Если элемент вернулся и снова уйдет, функция снова будет запущена.

HTML

<div ng-controller="MyCtrl" scrollable-container>
    <div ng-repeat="article in articles" class="block" scrollable-item="atNewArticle(article)">
        <h1>{{article.title}}</h1>
        <p>{{article.body}}</p>
    </div>
</div>

JS   var app = angular.module('myApp', []);

app.directive('scrollableContainer', function($window, $document) {
    return {
        link: function(scope, element, attrs) {
            angular.element($document).on('scroll', function() {
                var children = element.children();
                var $leftChild;
                for (var i = 0; i < children.length; i++) {
                    var $child = $(children[i]);
                    var childTop = $child.offset().top;
                    var docScrollTop = $document.scrollTop();
                    var scope = $child.scope();
                    if (childTop - docScrollTop < 0) {
                        if (!scope._left) {
                            scope._left = true;
                            $leftChild = $child;
                        }
                    } else {
                        delete scope._left;
                    }
                }
                if ($leftChild) {
                    $leftChild.scope().$eval( $leftChild.attr('scrollable-item') );
                }
            });
        }
    };
});


function MyCtrl($scope) {

    $scope.articles = [
        {title:'article1',body:'body of article1'},   
        {title:'article2',body:'body of article2'},
        {title:'article3',body:'body of article3'}
    ];

    $scope.atNewArticle = function (article) {
        //i only execute when transitioning back and forth between articles
        console.log(article);
    };
}