web-dev-qa-db-ja.com

ng-hide CSS遷移が完了するまでng-show要素を実行しませんか?

簡単な質問ですが、実装に問題があります。次のDOM設定がある場合:

<h1 class="fade" ng-repeat="child in parent.children" ng-show="parent.activeChild== child ">@{{ child.title }}</h1>

activeChildモデルのparentプロパティが変更されたときに、現在アクティブな子をフェードアウトするにはどうすればよいですかbeforeモデルが変更され、新しくアクティブになった子post-changeがフェードインします。

私はそれを大まかに機能させており、これを使用したCSSトランジションだけで

.fade.ng-hide-add {
    transition:opacity 1s ease;
}

.fade.ng-hide-remove {
    transition:opacity 1s ease 1s;
}

.fade.ng-hide-add {
    opacity:1;

    &.ng-hide-add-active {
        opacity:0;
    }
}

.fade.ng-hide-remove {
    opacity:0;

    &.ng-hide-remove-active {
        opacity:1;
    }
}

しかし、これは この問題(Plunkr) を生成することになります:

Gif

本質的に、私は自分のアニメーションを連鎖させたいです。 ng-animate docs を読んでみましたが、必要な効果を実現するために必要な構文に問題があります。

Angular docsには次のようなものがあります:

app.animation('.fade', [function() {
    return {
        addClass: function(element, className, doneFn) {
        },
        removeClass: function(element, className, doneFn) {
        }
    };
}]);
  • classNameとは何ですか?フェードイン/フェードアウト中に適用したいクラスですか?私が期待しているクラス?
  • doneFnとは何ですか?アニメーションが完了すると実行される関数だと思いますか?何が入ってるの?
  • すでにaddClassがある場合、removeClass関数とdoneFn関数で何をしますか?

目標

CSSまたはJSを使用して、AngularのngAnimateモジュールを直接使用して動作するアニメーションを生成したいと思います。どうすればこれを達成できますか?

33

見出しごとに別々の_<h1>_を使用する理由単一の_<h1>_タグを使用して、見出しを表示できます。

私はあなたの問題のデモを作成し、私はあなたの要求を無事に行いました。

更新済み

コードはngAnimateモジュールを使用するように編集されていることに注意してください。 ngAnimateモジュールを使用すると、要素を非表示にすると、クラス_.ng-hide_が作成されます。

これがアプリのコントローラーです。

_app2.controller("testController", ["$scope", "$timeout", function ($scope, $timeout) {

    $scope.heading = {};
    $scope.heading.show = true;

    $scope.parent = {};
    $scope.parent.children = ["A", "B", "C", "D"];
    $scope.parent.activeChild = "A";

    $scope.changeHeading = function (child) {
        $timeout(function () {
            $scope.parent.activeChild = child;
            $scope.heading.show = true;
        }, 1000);

    }
}]);
_

そして、あなたのhtmlページはこのように見えるはずです、

_<div ng-controller="testController">
    <h1 class="myAnimateClass" ng-show="heading.show" ng-class="{fadeIn : heading.fadeInModel==true, fadeOut : heading.fadeOutModel}"> {{parent.activeChild}} </h1>
    <p ng-repeat="child in parent.children" ng-click="heading.show = false;changeHeading(child)">{{child}}</p>
</div>
_

そして、CSS3を使用してフェードインとフェードアウトのアニメーションを実装しました。

_.myAnimateClass {
    -webkit-transition: opacity 1s ease-in-out;
    -moz-transition: opacity 1s ease-in-out;
    -o-transition: opacity 1s ease-in-out;
    -ms-transition: opacity 1s ease-in-out;
    transition: opacity 1s ease-in-out;
    opacity:1;
}

.myAnimateClass.ng-hide {
    opacity: 0;
}
_

説明

あなたの要件を達成するために、angularJSで_ng-class_と_$timeout_を使用しました。

見出しを表示するための_<h1>_タグは1つしかありません。見出しを変更するときは、バインディングプロパティ_$scope.parent.activeChild_を変更するだけです。

また、2つのスコープ変数_$scope.heading.fadeOutModel_および_$scope.heading.fadeInModel_を使用して、クラスfadeInおよびfadeOutを動的に追加および削除しました。

ユーザーがクリックして見出しを変更すると、クラスfadeOutが見出しに追加されました。つまり、フェードアウトのアニメーションが表示されます。また、app.jsで関数changeHeading()を呼び出しました。

ご覧のとおり、angularに_1000_ミリ秒待機してからフェードアウトアニメーションを終了させます。この時間が経過すると、選択した見出しが新しい見出しに置き換えられ、クラスが追加されます。 fadeIn。したがって、フェードインのアニメーションが開始されます。

これがあなたを助けることを願っています!!!

11

選択に応じて特定の要素を表示する、よりng-animateな方法は、ngSwitchを使用することです。このディレクティブは、スコープ式に基づいて、テンプレートのDOM構造を条件付きでスワップするために使用されます。これが です。

[〜#〜] html [〜#〜]

<button ng-repeat="item in items" ng-click="parent.selection = item">{{ item }}</button>
<div class="animate-switch-container" ng-switch on="parent.selection">
  <div class="animate-switch" ng-switch-when="foo">foo</div>
  <div class="animate-switch" ng-switch-when="bar">bar</div>
</div>

Javascript

$scope.items = ['foo', 'bar'];
$scope.parent = {
  selection: $scope.items[0]
}

[〜#〜] css [〜#〜]

.animate-switch-container {
  position:relative;
  height:40px;
  overflow:hidden;
}
.animate-switch {
  padding:10px;

}
.animate-switch.ng-animate {
  transition:opacity 1s ease;

}
.animate-switch.ng-leave.ng-leave-active,
.animate-switch.ng-enter {
  opacity: 0;
}
.animate-switch.ng-leave,
.animate-switch.ng-enter.ng-enter-active {
  opacity: 1;
}

これはチェーンではありませんが、AngularのngAnimateモジュールを直接使用して機能するアニメーションです。こちらも angularのウェブサイトでの例 です。

4
jjbskir

_.animation_を使用して、Javascriptベースのアニメーションを定義できます。たとえば、addClassおよびremoveClassの値として定義する関数

_app.animation('.fade', [function() {
    return {
        addClass: function(element, className, doneFn) {
        },
        removeClass: function(element, className, doneFn) {
        }
    };
}]);
_

Angularは、要素からクラスを追加または削除していることを検出すると、いずれかのメソッドから呼び出されます。

  • テンプレート内の_{{ }}_挿入。例えば。 _<span class="{{shouldFade ? 'fade' : ''}}">...._
  • テンプレートで_ng-class_を使用する。例えば。 _<span ng-class="{fade: shouldFade}">..._
  • ディレクティブで_$animate_サービスを使用する。例えば。 $animate.addClass(element, 'fade')または$animate.removeClass(element, 'fade')

ClassNameとは何ですか?フェードイン/フェードアウト中に適用したいクラスですか?私が期待しているクラス?

この例では、fadeになります。これは確かに少し奇妙ですが、これは、これが関係するクラス名であることはすでに明らかです。ただし、同じダイジェストサイクルで同じ要素に複数のクラスを追加する場合は、それらの連結がこの文字列として渡されます。

DoneFnとはどういう意味ですか?アニメーションが完了すると実行される関数だと思いますか?何が入ってるの?

これは、youが、定義したJavascriptアニメーションが完了すると呼び出される関数です。たとえば、何もしないアニメーションを定義するには:

_addClass: function(element, className, doneFn) {
  doneFn();
},
_

これを呼び出すと、Angularアニメーションが完了したことが通知されます。これにより、特に、要素から_ng-animate_クラスが削除されます。

次に、すでにdoneFnがある場合、addClass関数とremoveClass関数で何をしますか?

何らかの方法で要素を変更するために、おそらくタイムアウトまたはサードパーティのライブラリを使用して、それらにいくつかのコードを挿入します。完了したら、doneFnを呼び出します。たとえば、1ステップの不透明度「アニメーション」:

_addClass: function(element, className, doneFn) {
  element.css('opacity', 0.5);
  setTimeout(function() {
    doneFn();
  }, 1000);
},
_

CSSまたはJSを使用して、AngularのngAnimateモジュールを直接使用して動作するアニメーションを生成したいと思います。

これは実際には上記の答えとはあまり関係ありません!私が実際のケースを行っている場合、少なくとも他の(私が考えることができる)何かは少し過度に複雑であるため、要素を絶対的に配置することを強く疑います。

ただし、ngAnimateを使用してアニメーションをチェーンしたい場合、1つの可能な方法は、完了時に_$animate.addClass_および_$animate.removeClass_がpromiseを返すという事実を使用することです。要素を非表示にするときに返されるこのようなプロミスの最後につなげるには、ある種の中央の場所から呼び出され、どの要素が表示、非表示、表示されているかを追跡する必要があります。

これを行う方法は、2つのカスタムディレクティブを使用することです。 1つは表示および非表示にする各要素上にあり、ngShowのように使用できます。もう1つは、常に1つの要素のみを表示できるようにする親ディレクティブであり、_ng-hide_を追加した後に_ng-hide_クラス(および関連するアニメーション)をチェーンで削除します。ディレクティブは通信する必要があり、次の例のように、ngShowUniquengShowUniqueControllerのように呼び出すことができます。

_<div ng-show-unique-controller>
  <h1 class="fade" ng-repeat="child in parent.children" ng-show-unique="parent.activeChild == child">@{{child.title}}</h1>
</div>
_

これらは以下のように実装できます。

_app.directive('ngShowUniqueController', function($q, $animate) {
  return {
    controller: function($scope, $element) {
      var elements = [];
      var expressions = [];
      var watchers = [];
      var unregisterWatchers = null;
      var visibleElement = null;

      function registerWatchers() {
        unregisterWatchers = $scope.$watchGroup(expressions, function(vals) {
          var newCurrentIndex = vals.indexOf(true);
          var addPromise;

          if (visibleElement) {
            // Set a fixed height, as there is a brief interval between
            // removal of this class and addition of another
            $element.css('height', $element[0].getBoundingClientRect().height + 'px'); 
            addPromise = $animate.addClass(visibleElement, 'ng-hide');
          } else {
            addPromise = $q.when();
          }
          visibleElement = elements[newCurrentIndex] || null;
          if (!visibleElement) return;

          addPromise.then(function() {
            if (visibleElement) {
              $animate.removeClass(visibleElement, 'ng-hide').then(function() {
                $element.css('height', '');
              });
            }
          })
        });
      }   

      this.register = function(element, expression) {
        if (unregisterWatchers) unregisterWatchers();
        elements.Push(element[0]);
        expressions.Push(expression);
        registerWatchers();

        // Hide elements initially
        $animate.addClass(element, 'ng-hide');
      };

      this.unregister = function(element) {
        if (unregisterWatchers) unregisterWatchers();
        var index = elements.indexOf(element[0]);
        if (index > -1) {
          elements.splice(index, 1);
          expressions.splice(index, 1);
        }
        registerWatchers();
      };
    } 
  };
});

app.directive('ngShowUnique', function($animate) {
  return {
    require: '^ngShowUniqueController',
    link: function(scope, element, attrs, ngShowUniqueController) {
      ngShowUniqueController.register(element, function() {
        return scope.$eval(attrs.ngShowUnique);
      });

      scope.$on('$destroy', function() {
        ngShowUniqueController.unregister(element);
      });
    }
  };
});
_

これは http://plnkr.co/edit/1eJUou4UaH6bnAN0nJn7?p=preview で確認できます。私は認めざるを得ません、それはすべて少し気まぐれです。

4
Michal Charemza

問題は、H1がその親内に配置されたブロックレベルの要素であり、オーバーラップが許可されていないことです。そのため、表示されているアニメーションを押し下げて消えるアニメーションが表示されます。

これがここでより明確に行われていることがわかります: Demo

これを修正するには、ブロックレベルの要素H1を維持し、その位置を相対位置にして、ページの全体的なフローで相対位置を維持できるようにします。次に、子SPAN要素を絶対配置(親H1に対する絶対位置)に設定します。これにより、すべてのスパン要素が互いに重なり合うことができます。

[〜#〜] css [〜#〜]

.fade {
  opacity: 1;
  position: relative;
}

.fade.ng-hide-add {
    transition:opacity 1s ease;
    position: absolute;
}

.fade.ng-hide-remove {
    transition:opacity 1s ease 1s;
    position: absolute;
}

.fade.ng-hide-add {
  opacity:1;
}

.fade.ng-hide-add.ng-hide-add-active {
  opacity:0;
}

.fade.ng-hide-remove {
    opacity:0;
}

.fade.ng-hide-remove.ng-hide-remove-active {
    opacity:1;
}

[〜#〜] html [〜#〜]

  <body ng-controller="MainCtrl">
    <h1><span class="fade" ng-repeat="child in parent.children" ng-show="parent.activeChild == child ">@{{child.title}}</span></h1>
    <button ng-repeat="child in parent.children" ng-click="parent.activeChild = child">{{ child.title }}</button>
  </body>

ただし、1つの問題があります... SPAN要素には絶対配置があるため、アニメーション化するとフローから削除され、親H1はSPANコンテンツに合わせてサイズを変更できません。これにより、SPANが突然ジャンプします。

これに対処する方法(そして確かに少しハックです)は、SPANリピーターの後に空のスペースを追加することです。そのため、絶対配置のためにngRepeat SPANSが通常のフローから外れると、ngRepeatの外側にある空のスペースがH1の間隔を維持します。

これが動作する Plunker です。

1
pixelbits

最近のすべてのブラウザでサポートされている移行終了イベントを調べたい場合があります。

element.addEventListener('transitionend', callback, false);
1
tnt-rox

ngRepeatが1つの要素のみを表示するを使用することは、私の意見では、悪い考えです...要素を1つだけ表示しているためです! parent.activeChildプロパティを直接使用できます...

以下をご覧ください。

注:私はこのスニペットをわずか10分で作成しました。最適化されていないため、バグが発生する可能性があります...スターターとして使用できます:)

(function(window, angular, APP) {
  APP
    .value('menuObject', {
      name: 'Main Navigation',
      current: null,
      children: [{
        label: 'Don\'t ng-show element until ng-hide CSS transition is complete?',
        url: 'http://stackoverflow.com/questions/33336249/dont-ng-show-element-until-ng-hide-css-transition-is-complete',
        isCurrent: false
      },
      {
        label: 'Hitmands - Linkedin',
        url: 'http://it.linkedin.com/in/giuseppemandato',
        isCurrent: false
      },
      {
        label: 'Hitmands - Github',
        url: 'https://github.com/hitmands',
        isCurrent: false
      },
      {
        label: 'Hitmands - StackOverflow',
        url: 'http://stackoverflow.com/users/4099454/hitmands',
        isCurrent: false
      }
  ]})
  .directive('menu', function(menuObject, $q) {
    function menuCtrl($scope, $element) {
      $scope.parent = menuObject;
      
      this.getCurrentChild = function() {
        return $scope.parent.current;
      };
      this.getDomContext = function() {
        return $element;
      };
      this.setCurrentChild = function(child) {
        return $q.when($scope.parent)
        .then(function(parent) {
          parent.current = child;
          return parent;
        })
        .then(function(parent) {
          return parent.children.forEach(function(item) {
            item.isCurrent = child && (item.label === child.label);
          });
        })
      };
    }
    
    return {
      restrict: 'A',
      templateUrl: 'embedded-menutemplate',
      scope: {},
      controller: menuCtrl
    };
  })
  .directive('menuItem', function($animate, $q, $timeout) {
    
    function menuItemPostLink(iScope, iElement, iAttributes, menuCtrl) {
      iElement.bind('click', setCurrentTitle);
      iScope.$on('$destroy', function() {
        iElement.unbind('click', setCurrentTitle);
      })
      
      function setCurrentTitle(event) {
        event.preventDefault();
        var title;
        
        return $q
        .when(menuCtrl.getDomContext())
        .then(function(_menuElement) {
          title = angular.element(
            _menuElement[0].querySelector('#menuItemCurrent')
          );
        })
        .then(function() {
          return title.addClass('fade-out');
        })
        .then(function() {
          return $timeout(menuCtrl.setCurrentChild, 700, true, iScope.child);
        })
        .then(function() {
          return title.removeClass('fade-out');
        })
      }
    }
                                
    return {
      require: '^menu',
      link: menuItemPostLink,
      restrict: 'A'
    };
  })
;

})(window, window.angular, window.angular.module('AngularAnimationExample', ['ngAnimate']));
nav {
                text-align: center;
        }
        .link {
                display: inline-block;
                background-color: lightseagreen;
                color: black;
                padding: 5px 15px;
                margin: 1em;
        }
        #menuItemCurrent {
                padding: 1em;
                text-transform: uppercase;
                border: 1px solid black;
        }
        #menuItemCurrent span {
                transition: 500ms opacity linear;
                opacity: 1;
        }
        #menuItemCurrent.fade-out span {
                opacity: 0;
        }
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular-animate.js"></script>

<article ng-app="AngularAnimationExample">
  
  <nav menu></nav>

  <script id="embedded-menutemplate" type="text/ng-template">

        <nav >
                <a menu-item class="link" ng-repeat="child in parent.children track by $index" ng-bind="child.label"  ng-href="{{ child.url }}"></a>
        
                <h1 id="menuItemCurrent"><span  ng-bind="parent.current.url || 'NoMenuCurrentSelected'"></span></h1>    
                {{ parent.current || json }}
        </nav>
        
  </script>
</article>
1
Hitmands

これに対する素早い回答-過去にこの問題を解決するために、私は常にコンテンツを絶対的なものと位置づけてきました。このようにして、トランジションが発生しても、同じ位置にとどまります。

inlineまたはinline-blockの場合、コンテンツがdom内のスペースを占めるため、移行が完了するまでジャンプが表示されるので、他に方法はありません。

0
Chris