如何处理 Backbone .js应用程序中哈希更改的滚动位置
How to handle scroll position on hashchange in Backbone.js application?
我已经阅读了许多相关的线程,但它们似乎都没有提供解决方案。
我正在尝试做的是在我的 Backbone.js 应用程序中智能地处理滚动条。 像许多其他人一样,我有多个 #mypage 哈希路由。 其中一些路由是分层的。 例如,我有一个列出一些项目的 #list 页面,我点击列表中的一个项目。 然后它会打开一个 #view/ITEMID页面。
我的页面在 HTML 布局中共享相同的内容div。 在导航更改时,我将代表该路线视图的新div 注入到 Contentdiv 中,替换之前的任何内容。
所以现在我的问题:
如果该项目在列表中排名靠后,我可能需要滚动才能到达那里。 当我单击它时,"默认"主干行为是 #view/ITEMID 页面显示在与 #list 视图相同的滚动位置。 修复它很容易;只需在注入新视图时添加一个 $(document).scrollTop(0)。
问题是,如果我点击后退按钮,我想返回到之前的滚动位置的 #list 视图。
我试图采取明显的解决方案。 存储路线地图以在内存中滚动位置。 我在哈希更改事件的处理程序开始时写入此映射,但在新视图实际放入 DOM 之前。 在新视图位于 DOM 中之后,我从哈希更改处理程序末尾的地图中读取。
我注意到的是,至少在 Firefox 中的某个地方,某些东西正在滚动页面作为哈希更改事件的一部分,因此当我的"写入地图"代码被调用时,文档有一个不稳定的滚动位置,这绝对不是由用户明确制作的。
有人知道如何解决这个问题,或者我应该使用的最佳实践吗?
我仔细检查了一下,我的 DOM 中没有与我正在使用的哈希匹配的锚标签。
我的解决方案最终没有我想要的那么自动,但至少它是一致的。
这是我保存和恢复的代码。 这段代码几乎是从我的尝试带到我的实际解决方案中,只是在不同的事件上调用它。 "soft"是一个标志,它来自浏览器操作(后退,前进或哈希点击),而不是对Router.navigate()的"硬"调用。 在 navigate() 调用期间,我只想滚动到顶部。
restoreScrollPosition: function(route, soft) {
var pos = 0;
if (soft) {
if (this.routesToScrollPositions[route]) {
pos = this.routesToScrollPositions[route];
}
}
else {
delete this.routesToScrollPositions[route];
}
$(window).scrollTop(pos);
},
saveScrollPosition: function(route) {
var pos = $(window).scrollTop();
this.routesToScrollPositions[route] = pos;
}
我还修改了 Backbone.History,以便我们可以区分对"软"历史记录更改(调用 checkUrl)做出反应与以编程方式触发"硬"历史记录更改之间的区别。 它将此标志传递给路由器回调。
_.extend(Backbone.History.prototype, {
// react to a back/forward button, or an href click. a "soft" route
checkUrl: function(e) {
var current = this.getFragment();
if (current == this.fragment && this.iframe)
current = this.getFragment(this.getHash(this.iframe));
if (current == this.fragment) return false;
if (this.iframe) this.navigate(current);
// CHANGE: tell loadUrl this is a soft route
this.loadUrl(undefined, true) || this.loadUrl(this.getHash(), true);
},
// this is called in the whether a soft route or a hard Router.navigate call
loadUrl: function(fragmentOverride, soft) {
var fragment = this.fragment = this.getFragment(fragmentOverride);
var matched = _.any(this.handlers, function(handler) {
if (handler.route.test(fragment)) {
// CHANGE: tell Router if this was a soft route
handler.callback(fragment, soft);
return true;
}
});
return matched;
},
});
最初,我试图在哈希更改处理程序期间完全进行滚动保存和恢复。 更具体地说,在路由器的回调包装器中,调用实际路由处理程序的匿名函数。
route: function(route, name, callback) {
Backbone.history || (Backbone.history = new Backbone.History);
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (!callback) callback = this[name];
Backbone.history.route(route, _.bind(function(fragment, soft) {
// CHANGE: save scroll position of old route prior to invoking callback
// & changing DOM
displayManager.saveScrollPosition(foo.lastRoute);
var args = this._extractParameters(route, fragment);
callback && callback.apply(this, args);
this.trigger.apply(this, ['route:' + name].concat(args));
// CHANGE: restore scroll position of current route after DOM was changed
// in callback
displayManager.restoreScrollPosition(fragment, soft);
foo.lastRoute = fragment;
Backbone.history.trigger('route', this, name, args);
}, this));
return this;
},
我想以这种方式处理事情,因为它允许在所有情况下保存,无论是 href 单击、后退按钮、前进按钮还是 navigate() 调用。
浏览器有一个"功能",试图记住你在哈希更改上的滚动,并在返回哈希时移动到它。 通常这会很棒,并且可以省去我自己实现它的所有麻烦。 问题是我的应用程序,像许多应用程序一样,从一个页面到另一个页面更改 DOM 的高度。
例如,我在一个高 #list 视图上,并滚动到底部,然后单击一个项目并转到一个根本没有滚动条的短 #detail 视图。 当我按下"后退"按钮时,浏览器将尝试将我滚动到 #list 视图的最后一个位置。 但是文档还没有那么高,所以它无法做到这一点。 当我的 #list 路由被调用并重新显示列表时,滚动位置已丢失。
因此,无法使用浏览器的内置滚动内存。 除非我使文档具有固定的高度或做一些DOM技巧,否则我不想这样做。
此外,内置的滚动行为会破坏上述尝试,因为对 saveScrollPosition 的调用为时已晚 - 浏览器此时已经更改了滚动位置。
对此的解决方案应该是显而易见的,是从 Router.navigate() 调用 saveScrollPosition,而不是路由回调包装器。 这保证了我在浏览器对哈希更改执行任何操作之前保存滚动位置。
route: function(route, name, callback) {
Backbone.history || (Backbone.history = new Backbone.History);
if (!_.isRegExp(route)) route = this._routeToRegExp(route);
if (!callback) callback = this[name];
Backbone.history.route(route, _.bind(function(fragment, soft) {
// CHANGE: don't saveScrollPosition at this point, it's too late.
var args = this._extractParameters(route, fragment);
callback && callback.apply(this, args);
this.trigger.apply(this, ['route:' + name].concat(args));
// CHANGE: restore scroll position of current route after DOM was changed
// in callback
displayManager.restoreScrollPosition(fragment, soft);
foo.lastRoute = fragment;
Backbone.history.trigger('route', this, name, args);
}, this));
return this;
},
navigate: function(route, options) {
// CHANGE: save scroll position prior to triggering hash change
nationalcity.displayManager.saveScrollPosition(foo.lastRoute);
Backbone.Router.prototype.navigate.call(this, route, options);
},
不幸的是,这也意味着如果我对保存滚动位置感兴趣,我总是必须显式调用 navigate(),而不是在我的模板中只使用 href="#myhash"。
哦,好吧。 它有效。 :-)
一个简单的解决方案:将列表视图的位置存储在变量中的每个滚动事件上:
var pos;
$(window).scroll(function() {
pos = window.pageYOffset;
});
从项目视图返回时,将列表视图滚动到存储的位置:
window.scrollTo(0, pos);
我对此有一个有点穷人的解决方法。 在我的应用程序中,我遇到了类似的问题。 我通过将列表视图和项目视图放入一个容器中来解决它:
height: 100%
然后,我将列表视图和项目视图都设置为具有:
overflow-y: auto
height: 100%
然后,当我单击某个项目时,我会隐藏列表并显示项目视图。 这样,当我关闭项目并返回列表时,我将保留我在列表中的位置。 它只能使用后退按钮一次,尽管显然它不会保留您的历史记录,因此多次单击后退按钮不会将您带到您需要的位置。 尽管如此,一个没有 JS 的解决方案,所以如果它足够好......
@Mirage114 感谢您发布您的解决方案。它就像一个魅力。只是一件小事,它假设路由函数是同步的。如果存在异步操作,例如在呈现视图之前获取远程数据,则在将视图内容添加到 DOM 之前滚动窗口。就我而言,我在第一次访问路由时缓存数据。这样,当用户点击浏览器后退/前进按钮时,可以避免获取数据的异步操作。但是,可能并不总是可以缓存路由所需的每个数据。
- 如何在浏览器重新加载时保存位置哈希状态
- 在JavaScript中隐藏哈希位置
- 为所有哈希链接/调用的滚动位置添加自动偏移量
- 如何处理 Backbone .js应用程序中哈希更改的滚动位置
- 为什么从位置哈希中选择下拉列表在 Chrome 上有效,但不适用于 Firefox、IE 或 Safari
- 如何将哈希附加到 UIWebView 位置 href
- 如何区分浏览器返回和用户手动更改位置哈希
- 如何替换位置哈希并只保留最后一个历史记录条目
- 如何创建基于位置哈希的web应用程序
- 如何使社交按钮与位置.哈希工作
- 如何以更优雅的方式重写哈希赋值
- Firefox失败-使用文档后.写入和更新位置.哈希导致页面刷新
- 通过位置替换哈希.替换被认为有害的
- Javascript哈希在AJAX登录调用,更安全
- 的位置.哈希只在chrome和safari浏览器中生效一次
- 更改位置哈希后返回到前一个url
- Javascript:什么查找更快:数组.indexOf vs对象哈希值
- 更改位置哈希,onhashchange和内存泄漏
- Twitter引导标签的位置.哈希更改问题
- Extjs 4.的位置.没有显式id的哈希