Подтвердить что ты не робот

Как обрабатывать положение прокрутки в hashchange в приложении Backbone.js?

Я прочитал много связанных потоков, но ни один из них, похоже, не дает решения.

То, что я пытаюсь сделать, это разумно обработать полосу прокрутки в моем приложении Backbone.js. Как и многие другие, у меня есть несколько хэш-маршрутов #mypage. Некоторые из этих маршрутов являются иерархическими. например У меня есть страница #list, в которой перечислены некоторые элементы, я нажимаю на элемент в списке. Затем открывается страница # view/ITEMID.

Мои страницы имеют общий контент-контент в макете HTML. При изменении навигации я добавляю новый div, представляющий представление для этого маршрута, в Content div, заменяя все, что было до этого.

Итак, теперь моя проблема:

Если элемент находится далеко в списке, мне, возможно, придется прокручивать его. Когда я нажимаю на нее, поведение по умолчанию "по умолчанию" заключается в том, что страница # view/ITEMID отображается в той же позиции прокрутки, что и в представлении #list. Крепление достаточно просто; просто добавьте $(document).scrollTop(0) всякий раз, когда вводится новое представление.

Проблема в том, что если я нажму кнопку "Назад", я бы хотел вернуться к представлению #list в позиции прокрутки, которая была ранее.

Я попытался принять очевидное решение. Сохранение карты маршрутов для прокрутки позиций в памяти. Я пишу на эту карту в начале обработчика события hashchange, но до того, как новое представление фактически будет помещено в DOM. Я прочитал с карты в конце обработчика hashchange после того, как новое представление находится в DOM.

Что я замечаю, так это то, что что-то, где-то в Firefox, прокручивает страницу как часть события hashchange, так что к моменту, когда будет вызван код для записи в карту, у документа есть положение прокрутки, которое явно не было сделано пользователем.

Кто-нибудь знает, как это исправить, или наилучшей практикой, которую я должен использовать вместо этого?

Я дважды проверял, и в моем DOM нет привязанных тегов, которые соответствуют хешам, которые я использую.

4b9b3361

Ответ 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" в моих шаблонах.

Хорошо. Оно работает.: -)

Ответ 2

Простое решение: Сохраните позицию списка в каждом событии прокрутки в переменной:

var pos;
$(window).scroll(function() {
    pos = window.pageYOffset;
});

При возврате из представления элемента прокрутите список в сохраненное положение:

window.scrollTo(0, pos);

Ответ 3

У меня есть немного плохое решение для этого. В моем приложении у меня была аналогичная проблема. Я решил это, поместив представление списка и представление элемента в контейнер с помощью:

height: 100%

Затем я установил как представление списка, так и представление элемента:

overflow-y: auto
height: 100%

Затем, когда я нажимаю на элемент, я скрываю список и показываю представление элемента. Таким образом, когда я закрываю элемент и возвращаюсь к списку, я держу свое место в списке. Он работает с кнопкой "назад" один раз, хотя, очевидно, он не хранит вашу историю, поэтому множественные щелчки на кнопках "назад" не помогут вам, где вам нужно. Тем не менее, решение без JS, поэтому, если оно достаточно хорошо...

Ответ 4

@Mirage114 Спасибо, что опубликовали ваше решение. Отлично работает. Это лишь незначительная вещь, предполагается, что функции маршрута синхронны. Если есть операция async, например, извлечение удаленных данных перед визуализацией представления, тогда окно прокручивается до добавления содержимого представления в DOM. В моем случае я кэширую данные при первом посещении маршрута. Это так, что когда пользователь нажимает кнопку браузера назад/вперед, асинхронная операция выборки данных исключается. Однако не всегда возможно кэшировать все данные, необходимые для маршрута.