2012-06-26 12 views
27

多くの関連スレッドを読んだことがありますが、解決策を提示していないようです。Backbone.jsアプリケーションでハッシュ変更のスクロール位置を処理する方法は?

私がやっていることは、私のBackbone.jsアプリでスクロールバーを知的に扱うことです。他の多くの人々と同様に、私は複数の#mypageハッシュルートを持っています。これらのルートのいくつかは階層的です。例えばいくつかの項目をリストする#listページがあり、リスト内の項目をクリックします。次に、#view/ITEMIDページを開きます。

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

は、だから今私の問題:項目がはるかダウンリストである場合

私はそこに着くためにスクロールする必要がある場合があります。私がクリックすると、 "デフォルト"のバックボーンの動作は、#view/ITEMIDページが#listビューと同じスクロール位置に表示されるということです。修正は簡単です。新しいビューが挿入されるたびに$(document).scrollTop(0)を追加するだけです。

問題は、私が前のスクロール位置で#listビューに戻ってみたいということです。

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

少なくともFirefoxのどこかでhashyncイベントの一部としてページがスクロールされているので、書き込み用のコードが呼び出されるまでにドキュメントにはユーザーが明示的に明示的に作成したものではありませんでした。

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

私は二重チェックし、使用しているハッシュと一致するDOM内にアンカータグがありません。

+0

あなたは偶然 'position()。top'を使用しておらず、親要素がDOMのロード後に' position:anything'を取得していますか? – Ohgodwhy

+0

いいえ、私はスクロール位置を読み書きするために$(document).scrollTop()を使用していました – schematic

答えて

13

私の解決策は私の望むものよりも自動化されていませんが、少なくとも一貫しています。

これは保存と復元のためのコードです。このコードは、私の試みから実際のソリューションまで運ばれてきました。ちょうど異なるイベントで呼び出されました。 "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; 
} 

我々はプログラムで「ハード」履歴変更をトリガー対(checkUrlを呼び出す)「ソフト」履歴変更に反応の違いを伝えることができるように、私もBackbone.Historyを変更しました。このフラグをルータのコールバックに渡します。

_.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; 
    }, 
}); 

元々、私はスクロールの保存をやり直し、ハッシュ変更ハンドラ中に完全に復元しようとしていました。具体的には、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の高さをページごとに変更することです。

たとえば、#リストビューが高く、下にスクロールした後、アイテムをクリックして、スクロールバーがまったくない短い#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); 
}, 

残念ながら、それはまた、ちょうど私のテンプレートでのhref =「#1 myhash」を使用するのではなく、私は、スクロール位置を保存することに興味があれば、私はいつも)(明示的にナビゲートをコールする必要があるということです。

まあまあです。できます。 :-)

+0

私は小さな調整をする必要があったので、上記のオーバーライドコードのほとんどが元のバックボーンコードより多くのOOP-ishの方法では行えなかった既存のコードに変更することができます。 – schematic

0

私はこのために少し貧しい人の修正をしています。私のアプリでは、同様の問題がありました。そして、私が持っているリストビューとアイテムビューの両方を設定し

height: 100% 

overflow-y: auto 
height: 100% 

その後、私は上のクリックしたときに私がして容器にリストビューとアイテムビューを置くことによってそれを解決しましたアイテム、私はリストを非表示にしてアイテムビューを表示します。この方法でアイテムを閉じてリストに戻ると、私はリストに自分の場所を保持します。それはあなたの履歴を保持していませんが、バックボタンで一度動作しますので、バックボタンを複数回クリックするだけで、必要な場所にアクセスすることはできません。それでも、ノーJSと解決策、それは十分に良いことだので、もし...

5

シンプルなソリューション: ストア変数内のすべてのスクロールイベントのリストビューの位置:

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

から戻りますあなたのソリューションを投稿するための

window.scrollTo(0, pos); 
+0

ええ、これはうまくいき、私はscrollToを任意のonShowイベント(Marionette経由)に添付して、手動でページの上部にあることを確認することができます。 – MBHNYC

0

Mirage114 @ありがとう:アイテムビューは、保存された位置にあるリストビューをスクロールします。それは魅力のように働く。ちょっとしたことですが、ルート機能が同期していると想定しています。ビューをレンダリングする前にリモートデータをフェッチするなどの非同期操作がある場合、ビューの内容がDOMに追加される前にウィンドウがスクロールされます。私の場合は、初めてルートを訪れたときにデータをキャッシュします。これは、ユーザがブラウザの戻る/進むボタンを押すと、データを取得する非同期操作が回避されるようにするためである。ただし、ルートに必要なすべてのデータを常にキャッシュすることは不可能かもしれません。

関連する問題