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

Не показывать элемент ng-show до тех пор, пока ng-hide переход CSS не будет завершен?

Простой вопрос, но у меня проблемы с реализацией. Если у меня есть следующая настройка DOM:

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

Когда свойство activeChild модели parent изменяется, как я могу погасить текущий активный дочерний элемент, до, модель изменяет, а затем исчезает в новом активном дочернем после изменения.

Я работаю примерно так: только с помощью 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 имеют что-то вроде этого:

app.animation('.fade', [function() {
    return {
        addClass: function(element, className, doneFn) {
        },
        removeClass: function(element, className, doneFn) {
        }
    };
}]);
  • Что такое className? Является ли это классом, который я хочу применить, когда он исчезает? Класс, который я ожидаю?
  • Что означает doneFn? Я предполагаю, что это функция, которая запускается после завершения анимации? Что там происходит?
  • Что мне делать в addClass и removeClass, тогда, если у меня уже есть doneFn?

Цель

Я хотел бы сгенерировать рабочую анимацию непосредственно с помощью модуля Angular ngAnimate с помощью CSS или JS. Как я могу достичь этого?

4b9b3361

Ответ 1

Почему вы используете отдельный <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 для реализации fade in и fade out,

.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;
}

Объяснение

Чтобы достичь вашего требования, я использовал ng-class и $timeout в angularJS.

Вы можете видеть, что для отображения заголовка у меня есть только один тег <h1>. Когда я изменяю заголовок, я просто изменяю его свойство привязки $scope.parent.activeChild.

И я использовал две переменные области видимости $scope.heading.fadeOutModel и $scope.heading.fadeInModel для динамического добавления и удаления классов fadeIn и fadeOut.

Когда пользователь нажимает, чтобы изменить заголовок, я добавил класс fadeOut в ваш заголовок. Таким образом, это покажет анимацию исчезновения. А также я уволил функцию в app.js, changeHeading().

Вы можете видеть это, я заставил angular ждать 1000 миллисекунд, чтобы завершить анимацию затухания. По истечении этого времени он заменит выбранный заголовок на новый и добавит класс fadeIn. Таким образом, он начнет анимацию для постепенного перехода.

Надеюсь, это поможет вам!!!

Ответ 2

Более ng-анимационный способ отображения конкретного элемента в зависимости от выбора будет заключаться в использовании 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.

Ответ 3

Вы можете использовать .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. Это немного странно, так как в этом примере уже ясно, что это имя класса. Однако, если в том же цикле дайджеста вы добавляете несколько классов в один и тот же элемент, тогда их конкатенация передается как эта строка.

Что сделано? Должно быть? Я предполагаю, что это функция, которая запускается после завершения анимации? Что там происходит?

Это функция, которую вы вызываете, независимо от того, какая анимация Javascript вы определяете. Например, чтобы определить анимацию, которая ничего не делает, как все:

addClass: function(element, className, doneFn) {
  doneFn();
},

При вызове он сообщает Angular, что анимация завершена. Это, среди прочего, удалит класс ng-animate из элемента.

Что мне делать в функции addClass и removeClass, если у меня уже есть doneFn?

Вы вставляете в них некоторый код, возможно, используя тайм-ауты или стороннюю библиотеку, чтобы каким-то образом изменить элемент. Когда вы закончите, вы вызываете doneFn. Например, 1-кратная непрозрачность "анимация":

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

Я хотел бы сгенерировать рабочую анимацию непосредственно с помощью модуля Angular ngAnimate с CSS или JS.

Это не имеет особого отношения к ответам выше! Если бы я делал реальный случай, я бы сильно подозревал, что я бы поставил элементы абсолютно, как ничто другое (о чем я могу думать), по крайней мере, слишком сложно.

Однако, если вы действительно хотите связать анимацию с помощью ngAnimate, одним из возможных способов является использование того факта, что $animate.addClass и $animate.removeClass возвращает обещание, когда оно завершается. Чтобы связать конец обещания, возвращенного при скрытии элемента, его нужно вызвать из какого-то центрального местоположения и отслеживать, какой элемент видим, скрываться и отображаться.

Способ сделать это - использовать 2 пользовательских директивы. Один будет отображаться и скрываться для каждого элемента, который можно использовать очень как ngShow. Другая будет родительской директивой, которая позволит в любой момент видеть только один элемент и удалить цепочку ng-hide (и связанные с ней анимации) после любого добавления ng-hide. Директивам нужно будет общаться, можно назвать что-то вроде ngShowUnique и ngShowUniqueController, например, в следующем примере.

<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

используя ngRepeat, который покажет только один элемент, на мой взгляд, это плохая идея... потому что вы показываете только один элемент! вы можете напрямую использовать свойство parent.activeChild...

Взгляните на следующее:

Примечание. Я сделал этот сниппет всего за десять минут, он был неоптимизирован и может иметь некоторую ошибку... вы можете использовать его как стартер:)

(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>

Ответ 5

Проблема заключается в том, что H1 является элементом уровня блока, который помещен внутри него родительским и не допускается перекрытие. Вот почему вы видите одну анимацию, которая исчезает, выталкивая анимацию, которая появляется.

Вы можете видеть, что это происходит здесь более четко: Демо

Чтобы исправить это, вы хотите сохранить элемент уровня блока 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>

Есть одна проблема, хотя... Поскольку элементы SPAN имеют абсолютное позиционирование, они удаляются из потока при анимации, а родительский H1 не может изменять размер, чтобы соответствовать содержимому SPAN. Это приводит к неожиданному скачку SPAN.

Способ решения этой проблемы (и, по общему признанию, это немного взлома) заключается в добавлении пустого пространства после ретранслятора SPAN. Поэтому, когда ngRepeat SPANS извлекается из нормального потока из-за абсолютного позиционирования, пустое пространство, находящееся за пределами ngRepeat, сохраняет интервал H1.

Вот рабочий Plunker.

Ответ 6

Возможно, вы захотите просмотреть событие перехода, которое поддерживается всеми современными браузерами.

element.addEventListener('transitionend', callback, false);

Ответ 7

Быстрый ответ на этот вопрос. Чтобы решить эту проблему в прошлом, я всегда позиционировал контент абсолютным. Таким образом, когда переход происходит, он остается в том же положении.

Нет другого пути, потому что контент занимает пространство в dom, если его inline или inline-block, поэтому вы видите переход до завершения перехода