Выполнить функцию в контроллере после события прокрутки 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);
};
}