AngularJS: 状态改变后自动滚动到定位点

AngularJS: Auto-scroll to anchor point after state change

我希望让用户单击页面上的 link,这将触发 stateChange 并引导他们进入新状态。我想要实现的是当状态完成加载时,它会滚动到上一页 link 指定的页面上的锚点。

为了做到这一点,我使用了通过 ui-sref 传递 $stateParam 的组合,如下所示:

<a ui-sref="stateParent.stateChild({id: 'practiceParam'})">goToPage</a>

然后,当到达该页面时,上面有一个 div,附加了一个在 $viewContentLoaded 上激活的指令,所以 DOM 被呈现,我可以搜索一个 ID。 HTML div 看起来像这样:

<div scroll-after-load ></div>

我的滚动指令如下:

angular.module( 'app' ).directive('scrollAfterLoad', function() {
  return {
    restrict: 'A',
    link: function(scope, $elm, attrs, $stateParams) {
        scope.$on('$viewContentLoaded', function() {
            console.log('scrollAfterLoad Directive Loaded, $stateParams are: ', $stateParams );
            var idToScroll = attrs.href;
            var $target;
            if (idToScroll) {
                $target = $(idToScroll);
                // the -50 accounts for the top navbar which is fixed on the page and removed from pageflow
                $("html,body").animate({scrollTop: $target.offset().top - 50}, "slow");
            }
        });
    }
  };
});

我还没有费心在 div 上设置 href,但是因为我无法访问传递的参数,我是不是访问不正确?我也尝试使用更标准的 state.go() 来传递 stateParam 但我的结果仍然为空。

一旦我可以传递状态参数,我的想法就是将 href 添加到注入到新加载页面上的 div 的参数中,然后自动滚动到该页面上的另一个 div与我传递的参数匹配的 ID。

另请注意,在我的 ui-view 上,我将自动滚动设置为 true,因此页面会自动加载到顶部,我喜欢这种行为,这也是我需要状态来完成加载的原因在 scoll 被激活之前。

使用 angular-scroll 你可以这样做:

将您的 scroll target id 作为状态参数传递给子状态控制器,然后滚动到带有 scrollToAnimated(element) 的元素。

如果您想让它可重用,您可以创建该控制器的工厂并将其注入到子状态的解析中。那应该也可以,但我还没有测试过。

如果您处于子状态并再次单击状态更改 link,您可能需要重新触发滚动。这就是为什么我添加了方法 checkState 如果我们处于子状态以重复滚动则重新加载当前状态的原因。

请查看下面或此 fiddle 中的演示。

我认为你的指令不起作用,因为你没有将 href 属性传递给你的指令。

您可以通过将 href="targetId" 传递给您的指令来解决此问题,其中 targetId 在您的子控制器中添加了 $scope.targetId = $stateParams.id。或者,如果您不使用隔离范围,则可以直接访问 $scopetargetId

无论如何,我会像在演示中那样做,或者在 resolve 中使用工厂。

angular.module('demoApp', ['ui.router', 'duScroll'])
 .controller('MainControlller', MainController)
 .config(Config);

function MainController($scope, $state) {
 $scope.checkState = function() {
     //console.log($state.current);
        if($state.current.name == 'parent.child') {
            //console.log('parent.child state');
         // reload to retrigger scrolling again
           $state.reload();
        }
    };
}

function Config($urlRouterProvider, $stateProvider) {
 $urlRouterProvider.otherwise('/');
    
    $stateProvider
     .state('parent', {
         url: '/',
         templateUrl: 'home.html'
    })
     .state('parent.child', {
     url: 'child/:id',
        templateUrl: 'child.html',
        controller: function($scope, $stateParams, $document) {
         var scrollElement = angular
                .element(document.getElementById($stateParams.id));
            console.log(scrollElement);
            $document.scrollToElementAnimated(scrollElement);
        }
    });
               
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-scroll/0.7.3/angular-scroll.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.15/angular-ui-router.js"></script>
<div ng-app="demoApp" ng-controller="MainController">
    <script type="text/ng-template" id="home.html">
        <a ui-sref="parent.child({id: 'practiceParam'})" ng-click="checkState()">go to practice</a>
        <div ui-view=""></div>
    </script>
    <script type="text/ng-template" id="child.html">
        <!--<a href="#practiceParam" du-smooth-scroll="">scroll to practice</a>
        --><p>
            Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. 
        </p><p>
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. 
        </p><p>
Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.</p>
<p>
Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. 
        </p>
        <p>
            Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. 
        </p>
        <h1 id="practiceParam">practice</h1>
        <p>
Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. 
        </p><p>
Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.</p>
<p>
Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. 
        </p>
        <a ui-sref="parent">back</a>
    </script>
    
    <div ui-view=""></div>
</div>

您的问题是您试图将 $stateParams 服务注入 link 函数。但是,这种注入的正确位置是工厂方法,即指令的定义。工厂方法已在模块中注册 - 就像您使用 scrollAfterLoad 指令的情况一样。有关详细信息,请查看 AngularJS Dependency Injection.

所以回到你的问题:如果你实际上将 $stateParams 注入到你的指令的工厂方法中,意味着在顶部你将能够轻松访问参数。您的指令可能如下所示:

angular.module( 'app' ).directive('scrollAfterLoad', ['$stateParams', function($stateParams) {
  return {
    restrict: 'A',
    link: function(scope, $elm, attrs) {
        scope.$on('$viewContentLoaded', function() {
            console.log('scrollAfterLoad Directive Loaded, $stateParams are: ', $stateParams );
            var idToScroll = attrs.href;
            var $target;
            if (idToScroll) {
                $target = $(idToScroll);
                // the -50 accounts for the top navbar which is fixed on the page and removed from pageflow
                $("html,body").animate({scrollTop: $target.offset().top - 50}, "slow");
            }
        });
    }
  };
}]);

请注意,我已从您的 link 函数中删除 $stateParams 并将其放在顶部。这样,服务将在 link 函数中可用 - 实际上在整个指令中。

我在这个 Plunker 中重现了你的场景。