Ответ 1
Мое решение для этого оказалось чем-то менее автоматическим, чем я хотел, но, по крайней мере, он последователен.
Это был мой код для сохранения и восстановления. Этот код был в значительной степени перенесен из моей попытки перейти к моему фактическому решению, просто назвал его на разных событиях. "soft" - это флаг, который был получен из действия браузера (back, forward или hash click), а не "жесткого" вызова 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;
},
});
Изначально я пытался полностью сохранять и восстанавливать прокрутку во время обработчика hashchange. Более конкретно, в оболочке обратного вызова Router анонимная функция, которая вызывает ваш фактический обработчик маршрута.
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, кнопка "Назад", кнопка "Переслать" или "Навигация" ().
В браузере есть функция, которая пытается запомнить ваш свиток на хэш-замене и переходить к нему при возвращении к хэшу. Обычно это было бы здорово и избавило бы меня от всех проблем с его внедрением. Проблема в том, что мое приложение, как и многие, меняет высоту DOM со страницы на страницу.
Например, я нахожусь в высоком представлении #list и прокручиваю его вниз, затем выбираю элемент и перехожу к короткому представлению #detail, которое вообще не имеет полосы прокрутки. Когда я нажимаю кнопку "Назад", браузер попытается прокрутить меня до последней позиции, которая была для представления #list. Но документ еще не высок, поэтому он не может этого сделать. Когда мой маршрут для #list будет вызван, и я снова покажу список, позиция прокрутки будет потеряна.
Таким образом, не удалось использовать встроенную в прокрутку память браузера. Если бы я не сделал документ фиксированной высоты или сделал некоторые обманки DOM, которые я не хотел делать.
Кроме того, что встроенное поведение прокрутки помешает вышеприведенной попытке, потому что вызов saveScrollPosition сделан слишком поздно - браузер уже изменил положение прокрутки к тому времени.
Решение, которое должно было быть очевидным, заключалось в вызове saveScrollPosition из Router.navigate() вместо оболочки обратного вызова маршрута. Это гарантирует, что я сохраняю позицию прокрутки до того, как браузер сделает что-либо на hashchange.
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 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" в моих шаблонах.
Хорошо. Оно работает.: -)