27

私は多くの関連するスレッドを読みましたが、それらのどれも解決策を提供していないようです。

私がやろうとしているのは、Backbone.jsアプリでスクロールバーをインテリジェントに処理することです。他の多くのように、私は複数の#mypageハッシュルートを持っています。これらのルートの一部は階層的です。たとえば、いくつかのアイテムを一覧表示する#listページがあり、リスト内のアイテムをクリックします。次に、#view/ITEMIDページが開きます。

私のページはすべて、HTMLレイアウトで同じContentdivを共有しています。ナビゲーションの変更時に、そのルートのビューを表す新しいdivをContent divに挿入し、以前にあったものを置き換えます。

だから今私の問題:

アイテムがリストのはるか下にある場合は、そこに到達するためにスクロールする必要があるかもしれません。それをクリックすると、「デフォルト」のバックボーン動作は、#view/ITEMIDページが#listビューと同じスクロール位置に表示されることです。それを修正するのは簡単です。新しいビューが挿入されるたびに$(document).scrollTop(0)を追加するだけです。

問題は、戻るボタンを押すと、以前のスクロール位置で#listビューに戻りたいということです。

私はこれに対する明白な解決策をとろうとしました。ルートのマップをメモリ内のスクロール位置に保存します。hashchangeイベントのハンドラーの先頭で、新しいビューが実際にDOMに配置される前に、このマップに書き込みます。新しいビューがDOMに配置された後、hashchangeハンドラーの最後にあるマップから読み取りました。

私が気付いているのは、少なくともFirefoxのどこかで、ハッシュ変更イベントの一部としてページをスクロールしているため、マップへの書き込みコードが呼び出されるまでに、ドキュメントのスクロール位置が不安定になっていることです。ユーザーが明示的に作成したものではありません。

これを修正する方法、または代わりに使用する必要があるベストプラクティスを知っている人はいますか?

再確認しましたが、使用しているハッシュに一致するアンカータグがDOMにありません。

4

4 に答える 4

14

これに対する私の解決策は、私が望んでいたよりも自動化されていないものになりましたが、少なくとも一貫性があります。

これは、保存と復元のための私のコードでした。このコードは、私の試みから実際のソリューションにほとんど引き継がれ、さまざまなイベントで呼び出されました。「ソフト」は、Router.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を呼び出すことでした。これにより、ブラウザーが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'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);
},

残念ながら、テンプレートでhref = "#myhash"を使用するのではなく、スクロール位置を保存したい場合は、常に明示的にnavigate()を呼び出す必要があることも意味します。

しかたがない。できます。:-)

于 2012-07-01T18:02:36.847 に答える
5

簡単な解決策:すべてのスクロールイベントのリストビューの位置を変数に格納します。

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

アイテムビューから戻るときは、リストビューを保存された位置までスクロールします。

window.scrollTo(0, pos);
于 2013-06-04T19:08:53.857 に答える
0

私はこれに対して少し貧乏人の修正を持っています。私のアプリでも、同様の問題が発生しました。リストビューとアイテムビューを次のコンテナに入れることで解決しました。

height: 100%

次に、リストビューとアイテムビューの両方を次のように設定します。

overflow-y: auto
height: 100%

次に、アイテムをクリックすると、リストを非表示にしてアイテムビューを表示します。このようにして、アイテムを閉じてリストに戻ると、リスト内の位置を維持します。戻るボタンで1回動作しますが、明らかに履歴は保持されないため、戻るボタンを複数回クリックしても、必要な場所に移動できません。それでも、JSを使用しないソリューションなので、十分であれば...

于 2013-04-10T21:06:03.393 に答える
0

@Mirage114ソリューションを投稿していただきありがとうございます。それは魅力のように機能します。些細なことですが、ルート機能が同期していることを前提としています。ビューをレンダリングする前にリモートデータをフェッチするなどの非同期操作がある場合、ビューのコンテンツがDOMに追加される前にウィンドウがスクロールされます。私の場合、ルートに初めてアクセスしたときにデータをキャッシュします。これは、ユーザーがブラウザの戻る/進むボタンを押したときに、データをフェッチする非同期操作が回避されるようにするためです。ただし、ルートに必要なすべてのデータをキャッシュできるとは限りません。

于 2013-12-28T17:13:45.187 に答える