Подтвердить что ты не робот

Директива AngularJS для перехода к заданному элементу

У меня есть переменная scope $scope.first_unread_id, которая определена в моем контроллере. В моем шаблоне я:

<div id="items" >
  <ul class="standard-list">
    <li ng-repeat="item in items" scroll-to-id="first_unread_id">
    <span class="content">{{ item.content }}</span>
    </li>
  </ul>
</div>

и моя директива выглядит так:

angular.module('ScrollToId', []).
directive('scrollToId', function () {
  return function (scope, element, attributes) {
    var id = scope.$parent[attributes["scrollToId"]];
    if (id === scope.item.id) {
      setTimeout(function () {
        window.scrollTo(0, element[0].offsetTop - 100)
      }, 20);
    }
  }

});

он работает, однако, на два вопроса:

  • Есть ли лучший способ получить "first_unread_id" от области контроллера в прямой, чем область опроса. $parent? Это кажется немного "icky". Я надеялся, что смогу передать это через представление прямому параметру без необходимости повторять это на элементе li.

  • Есть ли лучший способ избежать необходимости вызова setTimeout()? Без него он работает иногда - я думаю, из-за разницы в сроках макета. Я понимаю, что синтаксис, который я использовал, определяет функцию ссылки, но мне не ясно, является ли это предварительной или пост-ссылкой по умолчанию, - и если это даже имеет значение для моей проблемы.

4b9b3361

Ответ 1

  • Вам не нужна область действия. $parent - поскольку она наследует значение из родительской области и когда она изменяется в родительской области, она будет передана вниз.
  • По умолчанию используется функция пост-ссылки. У вас есть какие-то изображения или что-то, что может привести к тому, что макет страницы изменится вскоре после начальной загрузки? Вы пробовали setTimeout без каких-либо затрат времени, например setTimeout (function() {})? Это позволит убедиться, что это будет "один за другим". Все остальное сделано.
  • Я бы также немного изменил логику вашей директивы, чтобы сделать ее более общей. Я бы выделил элемент, если данное условие истинно.

Вот эти 3 изменения:

HTML:

<div id="items" >
  <ul class="standard-list">
    <li ng-repeat="item in items" scroll-if="item.id == first_unread_id">
      <span class="content">{{ item.content }}</span>
    </li>
  </ul>
</div>

JS:

app.directive('scrollIf', function () {
  return function (scope, element, attributes) {
    setTimeout(function () {
      if (scope.$eval(attributes.scrollIf)) {
        window.scrollTo(0, element[0].offsetTop - 100)
      }
    });
  }
});

Ответ 2

Предполагая, что родительский элемент является тем, который мы прокручиваем, это работает для меня:

app.directive('scrollIf', function () {
  return function(scope, element, attrs) {
    scope.$watch(attrs.scrollIf, function(value) {
      if (value) {
        // Scroll to ad.
        var pos = $(element).position().top + $(element).parent().scrollTop();
        $(element).parent().animate({
            scrollTop : pos
        }, 1000);
      }
    });
  }
});

Ответ 3

В итоге я получил следующий код (который не зависит от jQ), который также работает, если прокручиваемый элемент не является окном.

app.directive('scrollIf', function () {
    var getScrollingParent = function(element) {
        element = element.parentElement;
        while (element) {
            if (element.scrollHeight !== element.clientHeight) {
                return element;
            }
            element = element.parentElement;
        }
        return null;
    };
    return function (scope, element, attrs) {
        scope.$watch(attrs.scrollIf, function(value) {
            if (value) {
                var sp = getScrollingParent(element[0]);
                var topMargin = parseInt(attrs.scrollMarginTop) || 0;
                var bottomMargin = parseInt(attrs.scrollMarginBottom) || 0;
                var elemOffset = element[0].offsetTop;
                var elemHeight = element[0].clientHeight;

                if (elemOffset - topMargin < sp.scrollTop) {
                    sp.scrollTop = elemOffset - topMargin;
                } else if (elemOffset + elemHeight + bottomMargin > sp.scrollTop + sp.clientHeight) {
                    sp.scrollTop = elemOffset + elemHeight + bottomMargin - sp.clientHeight;
                }
            }
        });
    }
});

Ответ 4

То же, что и принятый ответ, но использует встроенный метод javascript scrollIntoView":

angular.module('main').directive('scrollIf', function() {
    return function(scope, element, attrs) {
        scope.$watch(attrs.scrollIf, function(value) {
            if (value) {
                element[0].scrollIntoView({block: "end", behavior: "smooth"});
            }
        });
    }
});

Ответ 5

В сочетании с UI-маршрутизатором $uiViewScroll Я получил следующую директиву:

app.directive('scrollIf', function ($uiViewScroll) {
    return function (scope, element, attrs) {
        scope.$watch(attrs.scrollIf, function(value) {
            if (value) {
                $uiViewScroll(element);
            }
        });
    }
});

Ответ 6

В сочетании с @uri это работает для моего динамического содержимого с помощью ui-router и stateChangeSuccess в .run:

$rootScope.$on('$stateChangeSuccess',function(newRoute, oldRoute){

        setTimeout(function () {
            var postScroll = $state.params.postTitle;
            var element = $('#'+postScroll);
            var pos = $(element).position().top - 100 + $(element).parent().scrollTop();
            $('body').animate({
                scrollTop : pos
            }, 1000);
        }, 1000);

    });

Ответ 7

Чтобы получить ответы на лучшие ответы здесь, в ES6:

Файл: scroll.directive.js

export default function ScrollDirective() {
    return {
        restrict: 'A',
        scope: {
            uiScroll: '='
        },
        link: link
    };

    function link($scope, $element) {
        setTimeout(() => {
            if ($scope.uiScroll) {
                $element[0].scrollIntoView({block: "end", behavior: "smooth"});
            }
        });
    }
}

Файл scroll.module.js

import ScrollDirective from './scroll.directive';

export default angular.module('app.components.scroll', [])
    .directive('uiScroll', ScrollDirective);

После импорта в проект вы можете использовать его в своем html:

<div id="items" >
  <ul class="standard-list">
    <li ng-repeat="item in items" ui-scroll="true">
    <span class="content">{{ item.content }}</span>
    </li>
  </ul>
</div>