Javascript 保留 AngularJS 中路由更改的滚动位置?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14107531/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Retain scroll position on route change in AngularJS?
提问by
Sample app: http://angular.github.com/angular-phonecat/step-11/app/#/phones
示例应用程序:http: //angular.github.com/angular-phonecat/step-11/app/#/phones
If you choose the last phone "Motorola charm" it will show you the details of the phone. When you navigate back with on your browser it reloads the data and scrolling is at the top.
如果您选择最后一款手机“摩托罗拉魅力”,它会显示手机的详细信息。当您在浏览器上导航回时,它会重新加载数据并且滚动位于顶部。
What is the best way to automatically scroll to where is was left when navigatin back? And also, why does angular reloads the data?
导航返回时自动滚动到剩余位置的最佳方法是什么?而且,为什么 angular 会重新加载数据?
I have the same "angular-phonecat" sample on my computer and I have added an infinite scroll which loads more data as you scroll. So I really dont want the user to reload 50+ items again or scrolling down for 30 seconds.
我的计算机上有相同的“angular-phonecat”示例,并且我添加了一个无限滚动,它会在您滚动时加载更多数据。所以我真的不希望用户再次重新加载 50 多个项目或向下滚动 30 秒。
采纳答案by asgoth
I haven't used it before, but angular has a $anchorScrollservice. As to reloading the data, you could cache it using $cacheFactory, or store the data on a higher scope.
我以前没有使用过它,但是 angular 有一个$anchorScroll服务。至于重新加载数据,您可以使用$cacheFactory缓存它,或者将数据存储在更高的范围内。
回答by Joseph Oster
I have a fiddle here that shows how to restore scroll position in the list view after a detail view; not encapsulated in a directive yet, working on that...
我在这里有一个小提琴,它显示了如何在详细视图之后恢复列表视图中的滚动位置;尚未封装在指令中,正在处理...
$scope.scrollPos = {}; // scroll position of each view
$(window).on('scroll', function() {
if ($scope.okSaveScroll) { // false between $routeChangeStart and $routeChangeSuccess
$scope.scrollPos[$location.path()] = $(window).scrollTop();
//console.log($scope.scrollPos);
}
});
$scope.scrollClear = function(path) {
$scope.scrollPos[path] = 0;
}
$scope.$on('$routeChangeStart', function() {
$scope.okSaveScroll = false;
});
$scope.$on('$routeChangeSuccess', function() {
$timeout(function() { // wait for DOM, then restore scroll position
$(window).scrollTop($scope.scrollPos[$location.path()] ? $scope.scrollPos[$location.path()] : 0);
$scope.okSaveScroll = true;
}, 0);
});
The fiddle also shows fetching the list once, outside of 'ListCtrl'.
小提琴还显示了在“ListCtrl”之外获取列表一次。
回答by br2000
Below is another version of keep-scroll-pos directive. This version
下面是另一种版本的 keep-scroll-pos 指令。这个版本
Remembers scroll position of each templateUrlof your $routeProvider definition.
Respects hash tags, e.g., #/home#section-2, will scroll to #section-2not previous scroll position.
Is easy to use, as it is self-contained, and stores scroll positions internally.
记住$routeProvider 定义的每个templateUrl的滚动位置。
尊重哈希标签,例如 #/home# section-2,将滚动到#section-2而不是之前的滚动位置。
易于使用,因为它是独立的,并在内部存储滚动位置。
Example of html use:
html 使用示例:
<div ng-view keep-scroll-pos></div>
The code for keepScrollPos directive is below:
keepScrollPos 指令的代码如下:
"use strict";
angular.module("myApp.directives", [])
.directive("keepScrollPos", function($route, $window, $timeout, $location, $anchorScroll) {
// cache scroll position of each route's templateUrl
var scrollPosCache = {};
// compile function
return function(scope, element, attrs) {
scope.$on('$routeChangeStart', function() {
// store scroll position for the current view
if ($route.current) {
scrollPosCache[$route.current.loadedTemplateUrl] = [ $window.pageXOffset, $window.pageYOffset ];
}
});
scope.$on('$routeChangeSuccess', function() {
// if hash is specified explicitly, it trumps previously stored scroll position
if ($location.hash()) {
$anchorScroll();
// else get previous scroll position; if none, scroll to the top of the page
} else {
var prevScrollPos = scrollPosCache[$route.current.loadedTemplateUrl] || [ 0, 0 ];
$timeout(function() {
$window.scrollTo(prevScrollPos[0], prevScrollPos[1]);
}, 0);
}
});
}
});
To disregard previously stored scroll position, and to force to scroll to the top, use pseudo hash tag: #top, e.g., href="#/home#top".
要忽略先前存储的滚动位置,并强制滚动到顶部,请使用伪哈希标签:#top,例如 href=" #/home#top"。
Alternatively, if you prefer to just always scroll to the top, use built-in ng-view autoscrolloption:
或者,如果您更喜欢始终滚动到顶部,请使用内置的 ng-view autoscroll选项:
<div ng-view autoscroll></div>
回答by emp
I have used the solution of @Joseph Oster in order to create a directive. I have also taken the liberty to update the answer to use:
我使用了@Joseph Oster 的解决方案来创建指令。我还冒昧地更新了答案以使用:
- $locationChangeStart
- $locationChangeSuccess
- $locationChangeStart
- $locationChangeSuccess
as the other events are obsolete.
因为其他事件已经过时了。
Fiddle is here: http://jsfiddle.net/empie/p5pn3rvL/
小提琴在这里:http: //jsfiddle.net/empie/p5pn3rvL/
Directive source:
指令来源:
angular.module('myapp', ['ngRoute'])
.directive('autoScroll', function ($document, $timeout, $location) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.okSaveScroll = true;
scope.scrollPos = {};
$document.bind('scroll', function () {
if (scope.okSaveScroll) {
scope.scrollPos[$location.path()] = $(window).scrollTop();
}
});
scope.scrollClear = function (path) {
scope.scrollPos[path] = 0;
};
scope.$on('$locationChangeSuccess', function (route) {
$timeout(function () {
$(window).scrollTop(scope.scrollPos[$location.path()] ? scope.scrollPos[$location.path()] : 0);
scope.okSaveScroll = true;
}, 0);
});
scope.$on('$locationChangeStart', function (event) {
scope.okSaveScroll = false;
});
}
};
})
回答by Anton
i created a directive that works on the window scroll ( it could updated to work on any element though )
我创建了一个适用于窗口滚动的指令(它可以更新为适用于任何元素)
html usage
html用法
<div ng-keep-scroll="service.scrollY">
<!-- list of scrolling things here -->
</div>
where "service.scrollY" MUST be a variable within a service. Services retain their state and values, controllers are recreated every time they load and clear their values so you cant use them to store persistent data. the controller has a scope variable pointing to the service.
其中“service.scrollY”必须是服务中的变量。服务保留它们的状态和值,控制器在每次加载和清除它们的值时都会重新创建,因此您不能使用它们来存储持久数据。控制器有一个指向服务的作用域变量。
directive js
指令js
app.directive('ngKeepScroll', function ($timeout) {
return function (scope, element, attrs) {
//load scroll position after everything has rendered
$timeout(function () {
var scrollY = parseInt(scope.$eval(attrs.ngKeepScroll));
$(window).scrollTop(scrollY ? scrollY : 0);
}, 0);
//save scroll position on change
scope.$on("$routeChangeStart", function () {
scope.$eval(attrs.ngKeepScroll + " = " + $(window).scrollTop());
});
}
});
回答by Radu
Based on the great answer from br2000, I updated the directive code to work with ui-router. For states with same name but different params I serialize the $state.params object to make up a unique key in the scrollPosCacheobject.
基于 br2000 的出色回答,我更新了指令代码以使用 ui-router。对于名称相同但参数不同的状态,我将 $state.params 对象序列化以构成对象中的唯一键scrollPosCache。
.directive("keepScrollPos", function($state, $window, $timeout, $location, $anchorScroll) {
// cache scroll position of each route's templateUrl
var scrollPosCache = {};
// compile function
return function(scope, element, attrs) {
scope.$on('$stateChangeStart', function() {
// store scroll position for the current view
if ($state.current.name) {
scrollPosCache[$state.current.name + JSON.stringify($state.params)] = [ $window.pageXOffset, $window.pageYOffset ];
}
});
scope.$on('$stateChangeSuccess', function() {
// if hash is specified explicitly, it trumps previously stored scroll position
if ($location.hash()) {
$anchorScroll();
// else get previous scroll position; if none, scroll to the top of the page
} else {
var prevScrollPos = scrollPosCache[$state.current.name + JSON.stringify($state.params)] || [ 0, 0 ];
$timeout(function() {
$window.scrollTo(prevScrollPos[0], prevScrollPos[1]);
}, 0);
}
});
}
})
回答by nice ass
I made a version that works with any overflowed element, not just the document body:
我制作了一个适用于任何溢出元素的版本,而不仅仅是文档正文:
.directive("keepScrollPos", function($route, $timeout, $location, $anchorScroll) {
// cache scroll position of each route's templateUrl
var cache = {};
return {
restrict : 'A',
link: function($scope, elements, attrs){
$scope.$on('$routeChangeStart', function() {
// store scroll position for the current view
if($route.current)
cache[$route.current.loadedTemplateUrl + ':' + attrs.keepScrollPos] = [elements[0].scrollLeft, elements[0].scrollTop];
});
$scope.$on('$routeChangeSuccess', function(){
// if hash is specified explicitly, it trumps previously stored scroll position
if($location.hash()){
$anchorScroll();
return;
}
// else get previous scroll position and apply it if it exists
var pos = cache[$route.current.loadedTemplateUrl + ':' + attrs.keepScrollPos];
if(!pos)
return;
$timeout(function(){
elements[0].scrollLeft = pos[0];
elements[0].scrollTop = pos[1];
}, 0);
});
}
}
})
Use it like:
像这样使用它:
<div keep-scroll-pos="some-identifier"> ... </div>
回答by James Gwee
If your page requires fetching of data to display, you may have to use $routeChangeSuccess and delay the scrolling function call.
如果您的页面需要获取数据才能显示,您可能必须使用 $routeChangeSuccess 并延迟滚动函数调用。
scope.$on("$routeChangeSuccess", function() {
$timeout(function () {
var scrollY = parseInt(scope.$eval(attrs.ngKeepScroll));
$(window).scrollTop(scrollY ? scrollY : 0);
}, 1000); // delay by 1 sec
});
回答by Grzegorz Judas
I've found another simple way to solve this issue:
我找到了另一种简单的方法来解决这个问题:
var scrollValue = $(window).scrollTop();
$rootScope.$on("$routeChangeStart", function() {
scrollValue = $(window).scrollTop();
});
$rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
setTimeout(function() { $(window).scrollTop(scrollValue); }, 0);
});
Just put it in .run().
只需将它放在 .run() 中即可。
This way, setting timeout value to 0 it still works, but runs after the page is rendered (without timeout function it runs before the content (i.e. template or data loading) is rendered, making the function useless.
这样,将超时值设置为 0 仍然有效,但在页面呈现后运行(没有超时功能,它在内容(即模板或数据加载)呈现之前运行,使该功能无用。
If you fetch data from some API, you can wrap the timeout in a function in $rootScope and run it after successful request.
如果您从某个 API 获取数据,您可以将超时时间包装在 $rootScope 中的一个函数中,并在请求成功后运行它。
回答by JumpLink
You need to reset the scroll position on each route change. Use this in your main AppController:
您需要在每次路线更改时重置滚动位置。在您的主 AppController 中使用它:
$scope.$on("$routeChangeSuccess", function () {
$anchorScroll();
});
Or if you are using ui-route:
或者,如果您正在使用 ui-route:
$scope.$on("$stateChangeSuccess", function () {
$anchorScroll();
});
For more infomation see In AngularJS, how do I add a $watch on the URL hash?
有关更多信息,请参阅在 AngularJS 中,如何在 URL 哈希上添加 $watch?

