48

サンプルアプリ:http ://angular.github.com/angular-phonecat/step-11/app/#/phones

最後の電話「モトローラチャーム」を選択すると、電話の詳細が表示されます。ブラウザでに戻ると、データが再読み込みされ、スクロールが一番上に表示されます。

ナビゲートして戻ったときに残っていた場所に自動的にスクロールするための最良の方法は何ですか?また、Angularがデータをリロードするのはなぜですか?

私のコンピューターには同じ「angular-phonecat」サンプルがあり、スクロールするとより多くのデータをロードする無限スクロールを追加しました。したがって、ユーザーが50以上のアイテムを再度リロードしたり、30秒間下にスクロールしたりすることは本当に望ましくありません。

4

15 に答える 15

31

詳細ビューの後にリストビューのスクロール位置を復元する方法を示すフィドルがあります。まだディレクティブにカプセル化されていませんが、それに取り組んでいます...

http://jsfiddle.net/BkXyQ/6/

$scope.scrollPos = {}; // scroll position of each view

$(window).on('scroll', function() {
    if ($scope.okSaveScroll) { // false between $routeChangeStart and $routeChangeSuccess
        $scope.scrollPos[$location.path()] = $(window).scrollTop();
        //console.log($scope.scrollPos);
    }
});

$scope.scrollClear = function(path) {
    $scope.scrollPos[path] = 0;
}

$scope.$on('$routeChangeStart', function() {
    $scope.okSaveScroll = false;
});

$scope.$on('$routeChangeSuccess', function() {
    $timeout(function() { // wait for DOM, then restore scroll position
        $(window).scrollTop($scope.scrollPos[$location.path()] ? $scope.scrollPos[$location.path()] : 0);
        $scope.okSaveScroll = true;
    }, 0);
});

フィドルは、「ListCtrl」の外でリストを一度フェッチすることも示しています。

于 2013-04-24T17:29:17.643 に答える
17

以下は keep-scroll-pos ディレクティブの別のバージョンです。このバージョン

  • $routeProvider 定義の各templateUrlのスクロール位置を記憶します。

  • #/home# section-2などのハッシュタグを尊重し、前のスクロール位置ではなく#section-2にスクロールします。

  • 自己完結型で使いやすく、スクロール位置を内部に保存します。

HTML の使用例:

<div ng-view keep-scroll-pos></div>

keepScrollPos ディレクティブのコードは次のとおりです。

"厳密に使用";

angular.module("myApp.directives", [])

.directive("keepScrollPos", function($route, $window, $timeout, $location, $anchorScroll) {

    // 各ルートの templateUrl のスクロール位置をキャッシュする
    var scrollPosCache = {};

    // コンパイル関数
    return function(スコープ、要素、属性) {

        scope.$on('$routeChangeStart', function() {
            // 現在のビューのスクロール位置を保存します
            if ($route.current) {
                scrollPosCache[$route.current.loadedTemplateUrl] = [ $window.pageXOffset, $window.pageYOffset ];
            }
        });

        scope.$on('$routeChangeSuccess', function() {
            // ハッシュが明示的に指定されている場合、以前に保存されたスクロール位置より優先されます
            if ($location.hash()) {
                $anchorScroll();

            // それ以外の場合は、前のスクロール位置を取得します。何もない場合は、ページの上部までスクロールします
            } そうしないと {
                var prevScrollPos = scrollPosCache[$route.current.loadedTemplateUrl] || [ 0, 0 ];
                $timeout(関数() {
                    $window.scrollTo(prevScrollPos[0], prevScrollPos[1]);
                }、0);
            }
        });
    }
});

以前に保存されたスクロール位置を無視して、強制的に一番上にスクロールするには、疑似ハッシュ タグ#topを使用します (例: href=" #/home#top ")。

または、常に一番上までスクロールしたい場合は、組み込みの ng-view autoscrollオプションを使用します。

<div ng-view autoscroll></div>
于 2014-08-01T05:37:21.560 に答える
6

以前は使用したことがありませんが、angularには$anchorScrollサービスがあります。データのリロードに関しては、$ cacheFactoryを使用してデータをキャッシュするか、より高いスコープにデータを保存することができます。

于 2013-01-01T11:45:27.710 に答える
5

ウィンドウスクロールで機能するディレクティブを作成しました(ただし、任意の要素で機能するように更新できます)

htmlの使い方

<div ng-keep-scroll="service.scrollY">
<!-- list of scrolling things here -->
</div>

ここで、「service.scrollY」はサービス内の変数でなければなりません。サービスはその状態と値を保持します。コントローラーは値を読み込んでクリアするたびに再作成されるため、それらを使用して永続的なデータを保存することはできません。コントローラーには、サービスを指すスコープ変数があります。

ディレクティブ js

app.directive('ngKeepScroll', function ($timeout) {
    return function (scope, element, attrs) {

        //load scroll position after everything has rendered
        $timeout(function () {
            var scrollY = parseInt(scope.$eval(attrs.ngKeepScroll));
            $(window).scrollTop(scrollY ? scrollY : 0);
        }, 0);

        //save scroll position on change
        scope.$on("$routeChangeStart", function () {
            scope.$eval(attrs.ngKeepScroll + " = " + $(window).scrollTop());
        });
    }
});
于 2013-05-12T00:48:57.397 に答える
3

br2000 からの優れた回答に基づいて、ui-router で動作するようにディレクティブ コードを更新しました。名前が同じでパラメーターが異なる状態の場合、$state.params オブジェクトをシリアル化して、scrollPosCacheオブジェクト内の一意のキーを作成します。

.directive("keepScrollPos", function($state, $window, $timeout, $location, $anchorScroll) {

    // cache scroll position of each route's templateUrl
    var scrollPosCache = {};

    // compile function
    return function(scope, element, attrs) {

      scope.$on('$stateChangeStart', function() {
        // store scroll position for the current view
        if ($state.current.name) {
          scrollPosCache[$state.current.name + JSON.stringify($state.params)] = [ $window.pageXOffset, $window.pageYOffset ];
        }
      });

      scope.$on('$stateChangeSuccess', function() {
        // if hash is specified explicitly, it trumps previously stored scroll position
        if ($location.hash()) {
          $anchorScroll();

          // else get previous scroll position; if none, scroll to the top of the page
        } else {
          var prevScrollPos = scrollPosCache[$state.current.name + JSON.stringify($state.params)] || [ 0, 0 ];
          $timeout(function() {
            $window.scrollTo(prevScrollPos[0], prevScrollPos[1]);
          }, 0);
        }
      });
    }
  })
于 2015-03-13T08:29:51.333 に答える
1

ドキュメントの本文だけでなく、オーバーフローした要素で動作するバージョンを作成しました。

.directive("keepScrollPos", function($route, $timeout, $location, $anchorScroll) {

  // cache scroll position of each route's templateUrl
  var cache = {};

  return {
    restrict : 'A',
    link: function($scope, elements, attrs){

      $scope.$on('$routeChangeStart', function() {

        // store scroll position for the current view
        if($route.current)
          cache[$route.current.loadedTemplateUrl + ':' + attrs.keepScrollPos] = [elements[0].scrollLeft, elements[0].scrollTop];              

      });

      $scope.$on('$routeChangeSuccess', function(){
        // if hash is specified explicitly, it trumps previously stored scroll position
        if($location.hash()){
          $anchorScroll();
          return;
        }

        // else get previous scroll position and apply it if it exists
        var pos = cache[$route.current.loadedTemplateUrl + ':' + attrs.keepScrollPos];
        if(!pos)
          return;

        $timeout(function(){                  
          elements[0].scrollLeft = pos[0];
          elements[0].scrollTop = pos[1];            
        }, 0);

      });

    }
  }

})

次のように使用します。

<div keep-scroll-pos="some-identifier"> ... </div>
于 2017-04-30T14:18:04.703 に答える
0

他の回答とは異なり、スクロールだけでなくinputフィールドも覚えておきたいと思いましvalueた。

それだけでなく、彼らの多くは

  • 1 つのスクロール要素だけを覚えておきたい場合 (おそらく、ペインやその他のアプリのような表示がある場合)、
  • bodyスクロール要素として持っています(たとえば、角度スナップを使用している場合はどうなりますか?)、
  • または、スクロール要素が angular に置き換えられていません (つまり、 の外側にありますng-view)。
<body> <!-- doesn't scroll -->
    <div snap-drawers>..</div>

    <div snap-content="" history="scrollTop"> <!-- the scrolling div-->
        <header>...</header>

        <div ng-view>
            <input name="email" history="val"> <!-- tag with value we want remembered -->

            <div history="scrollLeft" history-watch="scroll" id="evenHorizontalScroll"><!--
                custom value we want remembered.
                NB it must have an id to be identified after angular
                removes it from the DOM between views,
                and since it is not a recognised default we can tell my
                directive the jquery event function what to watch
            --></div>
        </div>
    </div>
</body>

これらの問題を処理する共有スコープ ディレクティブを [残念ながらもっと長く] 書きました。

.directive('history', function($compile, $rootScope, $location) {
    return {
        restrict : 'A',
        replace : false,
        scope : false,

        controller : function($scope, $timeout) {
            //holds all the visited views
            var states = new Object();
            //the current view
            var state = null;
            //how many names have been generated where the element itself was used
            var generated = 0;

            //logs events if allowed
            function debug(from) {
                //comment this to watch it working
                //return;

                console.log('StateHistory: ' + from);
                if (from == 'went')
                    console.log(state);
            }

            //applies the remembered state
            function apply() {
                var element;
                //for each item remembered in the state
                for (var query in state) {
                    //use the element directly, otherwise search for it
                    (state[query].element || $(query))
                        //use the appropriate function
                        [state[query].property](
                            //and set the value
                            state[query].value
                        )
                    ;
                    debug('applying:' + query + ':' + state[query].value);
                }

                //start recording what the user does from this point onward
                $scope.ignore = false;
            }

            //generates a reference we can use as a map key
            $scope.generateRef = function() {
                return '' + (++generated);
            };

            //views changed
            $scope.went = function() {
                debug('went');

                //set the current state
                state = states[$location.path()];

                //if we dont remember the state of the page for this view
                if (!state)
                    //get recording!
                    state = states[$location.path()] = new Object();

                //apply the state after other directives
                //(like anchorScroll + autoscroll) have done their thing
                $timeout(apply);
            };

            //one of the elements we're watching has changed
            $scope.changed = function(name, element, property, useObject) {
                //if we're not meant to be watching right now
                //i.e. if the user isnt the one changing it
                if ($scope.ignore) {
                    debug('ignored');
                    return;
                }

                //if we havent recorded anything for this here yet
                if (!state[name]) {
                    //start recording
                    state[name] = {property:property};

                    //and remember to leave behind a reference if the name isn't
                    //good enough (was generated)
                    if (useObject)
                        state[name].element = element;
                }

                //use the requested function to pull the value
                state[name].value = element[property]();

                debug('changed:' + name + ':' + state[name].value);
            };

            //initial view
            $scope.went();

            //subsequent views
            $rootScope.$on('$routeChangeSuccess', $scope.went);
            $rootScope.$on('$routeChangeError', $scope.went);

            $rootScope.$on('$routeChangeStart', function() {
                debug('ignoring');
                $scope.ignore = true;
            });
        },

        link: function (scope, element, attrs) {
            //jquery event function name
            var watch = attrs.historyWatch;
            //if not set, use these defaults
            if (!watch) {
                switch (attrs.history) {
                case 'val':
                    watch = 'change';
                    break;
                case 'scrollTop':
                    watch = 'scroll';
                    break;
                default:
                    watch = attrs.history;
                }
            }

            //the css selector to re-find the element on view change
            var query = null;
            //the reference to the state remembered
            var name;

            //try using the id
            if (attrs.id)
                name = query = '#' + attrs.id;
            //try using the form name
            else if (attrs.name)
                name = query = '[name=' + attrs.name + ']';
            //otherwise we'll need to just reference the element directly
            //NB should only be used for elements not swapped out by angular on view change,
            //ie nothing within the view. Eg the view itself, to remember scrolling?
            else
                name = scope.generateRef();

            //jquery value function name
            var property = attrs.history;

            //watch this element from here on out
            element.on(watch, function() {
                scope.changed(name, element, property, !query);
            });
        }
    };
})
于 2016-02-12T01:32:20.147 に答える
0

これはあなたの問題を解決するかもしれません、それは私のために働きます $httpProvider.defaults.cache = true;

于 2015-11-17T12:29:23.410 に答える
0

この問題を解決する別の簡単な方法を見つけました。

var scrollValue = $(window).scrollTop();

$rootScope.$on("$routeChangeStart", function() {
    scrollValue = $(window).scrollTop();
});

$rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
    setTimeout(function() { $(window).scrollTop(scrollValue); }, 0);
});

.run() に入れるだけです。

このように、タイムアウト値を 0 に設定しても機能しますが、ページがレンダリングされた後に実行されます (タイムアウト機能がないと、コンテンツ (つまり、テンプレートまたはデータの読み込み) がレンダリングされる前に実行されるため、機能が役に立たなくなります)。

一部の API からデータをフェッチする場合、タイムアウトを $rootScope の関数にラップし、リクエストが成功した後に実行できます。

于 2014-04-28T10:18:10.603 に答える
0

ルート変更ごとにスクロール位置をリセットする必要があります。これをメインの AppController で使用します。

  $scope.$on("$routeChangeSuccess", function () {
    $anchorScroll();
  });

または、ui-route を使用している場合:

  $scope.$on("$stateChangeSuccess", function () {
    $anchorScroll();
  });

詳細については、AngularJS で URL ハッシュに $watch を追加するにはどうすればよいですか? を参照してください。

于 2014-10-13T14:51:57.497 に答える
0

@br2000による優れたソリューション。

ただし、残念ながら、スクロールして戻っていたページは、ディレクティブが位置を復元しようとしたときに、バックエンドから長いリストにデータをロードしていました。

したがって、明らかにスクロール位置の復元に失敗しました。$intervalの代わりにを使用して解決し、$timeoutで 20 回繰り返しました300ms timeout。返された約束を保存し、現在の位置が保存された位置と同じかどうかを関数$interval内でチェックし$interval、そうであれば、スコープメソッドを呼び出して$interval - $interval.cancel(promise).

さらに、が のルートに適用されたため、最初は mypageYOffsetpageXOffsetは常に 0でした。ルートを別のルートにラップして、このディレクティブを配置することで解決しました。overflow-x: hiddendivDOMdivdiv

于 2016-12-14T14:03:21.197 に答える