如何以正确的方式在angular.js中实现scrollspy?

时间:2013-07-04 12:29:51

标签: twitter-bootstrap jquery-plugins angularjs

有没有人对在角度应用程序中实现scrollspy的最佳方法有任何想法?我从Twitter Bootstrap's implementation开始,但我很难找到一个初始化(更重要的是在DOM更改后刷新)插件的好地方。

基本上我的控制器在加载数据时会对数据执行异步请求,并且在请求返回之前无法完全呈现DOM。然后,当完全呈现DOM时,需要刷新scrollspy插件。现在,一旦收到所需的数据,我的控制器就会在其范围内广播一个事件,并且我有一个指令来接收此事件。我不太喜欢这个解决方案有两个原因

  1. 感觉很讨厌。

  2. 当我在收到此事件后刷新scrollspy插件时,由于DOM未在周期的后期更新,因此它还为时尚早。我尝试了evalAsync,但最后我不得不使用超时,只希望DOM渲染得足够快。

  3. 我查看了the Bootstrap plugin的来源,从头开始实现这一点似乎相当简单。我遇到的问题是,当我试图为它做一个指令时,我似乎无法从链接函数中收到的元素订阅滚动事件。

    有没有人找到这样的好方法,或者有人有任何建议吗?我与使用Bootstrap实现无关,截至目前我在Bootstrap上唯一的dep是我刚刚添加的scrollspy-plugin。

3 个答案:

答案 0 :(得分:19)

亚历山大·希尔发表了一篇文章,描述了Bootstrap的ScrollSpy的AngularJS实现:http://alxhill.com/blog/articles/angular-scrollspy/

我已将他的CoffeeScript代码翻译成JavaScript,修复了一些错误,添加了一些安全检查和一个额外的功能以便进行测量。这是代码:

app.directive('scrollSpy', function ($window) {
  return {
    restrict: 'A',
    controller: function ($scope) {
      $scope.spies = [];
      this.addSpy = function (spyObj) {
        $scope.spies.push(spyObj);
      };
    },
    link: function (scope, elem, attrs) {
      var spyElems;
      spyElems = [];

      scope.$watch('spies', function (spies) {
        var spy, _i, _len, _results;
        _results = [];

        for (_i = 0, _len = spies.length; _i < _len; _i++) {
          spy = spies[_i];

          if (spyElems[spy.id] == null) {
            _results.push(spyElems[spy.id] = elem.find('#' + spy.id));
          }
        }
        return _results;
      });

      $($window).scroll(function () {
        var highlightSpy, pos, spy, _i, _len, _ref;
        highlightSpy = null;
        _ref = scope.spies;

        // cycle through `spy` elements to find which to highlight
        for (_i = 0, _len = _ref.length; _i < _len; _i++) {
          spy = _ref[_i];
          spy.out();

          // catch case where a `spy` does not have an associated `id` anchor
          if (spyElems[spy.id].offset() === undefined) {
            continue;
          }

          if ((pos = spyElems[spy.id].offset().top) - $window.scrollY <= 0) {
            // the window has been scrolled past the top of a spy element
            spy.pos = pos;

            if (highlightSpy == null) {
              highlightSpy = spy;
            }
            if (highlightSpy.pos < spy.pos) {
              highlightSpy = spy;
            }
          }
        }

        // select the last `spy` if the scrollbar is at the bottom of the page
        if ($(window).scrollTop() + $(window).height() >= $(document).height()) {
          spy.pos = pos;
          highlightSpy = spy;
        }        

        return highlightSpy != null ? highlightSpy["in"]() : void 0;
      });
    }
  };
});

app.directive('spy', function ($location, $anchorScroll) {
  return {
    restrict: "A",
    require: "^scrollSpy",
    link: function(scope, elem, attrs, affix) {
      elem.click(function () {
        $location.hash(attrs.spy);
        $anchorScroll();
      });

      affix.addSpy({
        id: attrs.spy,
        in: function() {
          elem.addClass('active');
        },
        out: function() {
          elem.removeClass('active');
        }
      });
    }
  };
});

来自Alexander博客文章的HTML片段,展示了如何实施该指令:

<div class="row" scroll-spy>
  <div class="col-md-3 sidebar">
    <ul>
        <li spy="overview">Overview</li>
        <li spy="main">Main Content</li>
        <li spy="summary">Summary</li>
        <li spy="links">Other Links</li>
    </ul>
  </div>
  <div class="col-md-9 content">
    <h3 id="overview">Overview</h3>
    <!-- overview goes here -->
    <h3 id="main">Main Body</h3>
    <!-- main content goes here -->
    <h3 id="summary">Summary</h3>
    <!-- summary goes here -->
    <h3 id="links">Other Links</h3>
    <!-- other links go here -->
  </div>
</div>

答案 1 :(得分:11)

在Angular中使用Scrollspy存在一些挑战(可能为什么AngularUI在2013年8月仍未包含Scrollspy)。

每次对DOM内容进行任何更改时,都必须刷新scrollspy。

这可以通过$观察元素并为scrollspy('refresh')调用设置超时来完成。

如果您希望能够使用nav元素导航可滚动区域,还需要覆盖导航元素的默认行为。

这可以通过在锚元素上使用preventDefault来阻止导航并通过将scrollTo函数附加到ng-click来实现。

我在Plunker上抛出了一个有效的例子:http://plnkr.co/edit/R0a4nJi3tBUBsluBJUo2?p=preview

答案 2 :(得分:5)

我在github上找到了一个项目,可以添加scrollspy,动画scrollTo和滚动事件。

https://github.com/oblador/angular-scroll

您可以在这里查看live demo,这是example source

您可以使用bower进行安装

$ bower install angular-scroll