How can I delay the ng-show element until the ng-hide CSS transition has finished?

Looking for a simple solution to my implementation issues. I'm faced with the following DOM setup:

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

How can I smoothly fade out the currently active child when the activeChild property of the parent model changes, and then fade in the newly active child afterward?

I have a rough working solution using CSS transitions:

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

However, this leads to an issue as shown in this example (Plunkr).

I aim to chain my animations. Although I've read the ng-animate docs, I'm struggling with the syntax required to achieve the desired effect.

The Angular docs feature something like this:

app.animation('.fade', [function() {
    return {
        addClass: function(element, className, doneFn) {
        },
        removeClass: function(element, className, doneFn) {
        }
    };
}]);
  • What is className? Is it the class intended for fading in/out? The expected class?
  • What does doneFn represent? Does it signify a function executed after the animation finishes? What should be included here?
  • If there is already a doneFn, what should be included in the addClass and removeClass functions?

The Objective

I want to create a functional animation directly using Angular's ngAnimate module, utilizing either CSS or JS. Any tips on achieving this goal?

Answer №1

What is the reason behind using a separate <h1> tag for each heading? It is possible to achieve the same result with just one <h1> tag.

I have developed a demonstration to address your issue and successfully met your requirements.

Recent Updates

Please note that I have made modifications to incorporate the ngAnimate module. Utilizing this module will generate a class called .ng-hide when an element is hidden.

Below is the controller code snippet for your application:

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);

    }
}]);

Additionally, your HTML page structure should resemble the following code:

<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 has been used to implement the fade in and fade out animation effects:

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

Explanation

To satisfy your requirements, I've employed ng-class and $timeout within AngularJS.

You'll notice that I'm utilizing only one <h1> tag to display your headings. Changing the heading involves modifying its binding property $scope.parent.activeChild.

In order to dynamically add and remove classes fadeIn and fadeOut, two scope variables $scope.heading.fadeOutModel and $scope.heading.fadeInModel are utilized.

Upon clicking to change the heading, I apply the class fadeOut to initiate the fade out animation. Additionally, the function changeHeading() in app.js is executed.

By delaying execution for 1000 milliseconds, the AngularJS waits for the fade out animation to conclude. Subsequently, the selected heading is replaced with the new one while adding the class fadeIn to commence the fade in animation.

I trust this explanation proves beneficial to you!

Answer №2

To display a specific element based on a selection in a more dynamic way, consider implementing ngSwitch in AngularJS. This directive allows you to conditionally switch the DOM structure in your template depending on a scope expression. You can see an example here.

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

This is not chaining, but it is a working animation directly using Angular's ngAnimate module. You can also find another example of ngSwitch on Angular's website.

Answer №3

To create animations based on Javascript, you can utilize the .animation feature. For instance, you can define functions as values for addClass and removeClass.

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

Angular triggers these functions when a class is added or removed from an element through various methods such as:

  • {{ }} interpolation in a template. Example:
    <span class="{{shouldFade ? 'fade' : ''}}">....
  • Using ng-class in a template. Example:
    <span ng-class="{fade: shouldFade}">...
  • Using the $animate service in a directive. Example:
    $animate.addClass(element, 'fade')
    or
    $animate.removeClass(element, 'fade')

What exactly is className? Is it the class to be applied while fading in/out? The expected class?

In this scenario, the value of className will be fade. Though redundant in this example, if multiple classes are being added in the same cycle, they will be concatenated into this string.

What is the purpose of doneFn? Is it a function that executes once the animation finishes? What should be included in it?

doneFn is a function that you call after your defined Javascript animation completes. For a simple animation that does nothing:

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

Calling doneFn signals Angular that the animation has finished, leading to the removal of the ng-animate class.

If doneFn is already present, what tasks should be performed within the addClass and removeClass functions?

You need to include code in them, possibly involving timeouts or external libraries, to modify the element. Upon completion, call doneFn. For instance, a simple opacity animation:

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

I want to produce a functional animation directly using Angular's ngAnimate module, with CSS or JS. How can I do this?

This question diverges from the previous answers! Realistically, positioning elements absolutely might be the simplest solution for smooth animations. However, if chaining animations through ngAnimate is desired, one approach involves utilizing promises returned by $animate.addClass and $animate.removeClass. This process requires coordinating elements visibility and transitions centrally, as demonstrated below.

To achieve this chain of animations through ngAnimate, consider implementing two custom directives - ngShowUnique and ngShowUniqueController, which only display a single element at a time and manage the associated animations.

The implementation details can be found in the provided code snippet.

Explore the working example at http://plnkr.co/edit/1eJUou4UaH6bnAN0nJn7?p=preview. It may seem complex, but it offers control over element visibility and animations.

Answer №4

While using ngRepeat to display only one element at a time may seem inefficient, I believe it serves its purpose by focusing on one element at a time. You have the option to utilize the parent.activeChild property directly...

Check out the following:

Note: This snippet was created in just ten minutes and may not be optimized or bug-free...feel free to use it as a starting point :)

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

Answer №5

The issue at hand involves the block-level element H1 being positioned within its parent without any overlapping allowed. As a result, one animation disappearing causes it to push down the appearing animation.

A clearer demonstration of this problem can be seen here.

To resolve this issue, the recommendation is to maintain the block-level nature of element H1 but set its position to relative so it retains its place in the page flow. Then, adjust the child SPAN elements to have absolute positioning relative to their parent H1, allowing for proper overlap between them.

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>

However, a potential downside arises due to the SPAN elements having absolute positioning, causing them to be removed from the flow during animation and preventing the parent H1 from resizing accordingly. This leads to unexpected jumps in the layout.

To address this issue, a workaround involves adding an empty space after the SPAN repeater. This way, even when the ngRepeated SPANS are taken out of the normal flow due to absolute positioning, the empty space outside the ngRepeat maintains the spacing of the H1.

You can view a functioning example on Plunker.

Answer №6

Consider exploring the transitionend event, which is compatible with all contemporary web browsers.

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

Answer №7

Here's a quick solution - Whenever I've encountered this issue before, I've found success by positioning the content absolutely. This ensures that during the transition, the content remains in its original position.

Unfortunately, there isn't another workaround for this because if the content is set to inline or inline-block, it still takes up space in the DOM. This is why you may notice a jump until the transition is complete.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Tips for embedding a custom directive within a ui-bootstrap tooltip

Here is the code snippet that I am working with: // Code Example <span> hello world! </span> // Custom Directive 'use strict'; var referenceFieldTemplate = require('./reference-field.html'); module.exports = direct ...

Building a DOM element using jQuery

I have a function $(document).ready(function () { $("#btnhighlight").click(function () { var htext = $("#txthighlighttext").val(); $("#lstCodelist option").each(function () { var sp = $(this).text(); ...

Developing desktop applications using C# scripting

I currently have a C# desktop program that is able to work with new C# plugins. My goal is to modify the existing C# application to allow for scripts to be used as plugins. These scripts could be in JavaScript, Windows Script Host (WSh), or any other form ...

What is the best way to sort a combination of numbers and text strings?

Within my table, I have column names enclosed in <th> tags and only numerical values inside <td> tags. When the user clicks on a <th> tag, the sorting algorithm organizes the values in ascending or descending order. .sortElements(function ...

What is the best way to dynamically search and retrieve data from a JSON object in Angular?

I am facing a challenge with my Angular (v. 1.6.3) app where I have fetched a JSON object containing stock price data. The structure of the JSON object only allows querying using brackets, with each key being a string that may include spaces, parentheses, ...

Avoid the automatic scrolling of a datatable to the top when clicking on a button within a column using jQuery

Is there a method to stop the datatable from automatically scrolling to the top when any of the buttons is clicked? ...

There is no way to convert a strongly typed object into an observable object using MobX

I have been utilizing the MobX library in conjunction with ReactJS, and it has integrated quite smoothly. Currently, I am working with an observable array structured as follows: @observable items = []; When I add an object in the following manner, everyt ...

Retrieve various URLs within an object using React

My task involves extracting all URLs from a specific object. Object { "Info": "/api/2", "Logo": "/api/2/Logo", "Photo": "/api/2/photo", } I aim to store the responses in a state, ensuring t ...

If a particular class is present on an element

Is it feasible to set an ng-if directive to true based on the presence of a specific class in an element? For example: <div class="edge" ng-repeat="item in items"> <div ui-view ng-if="(ng-repeat div has class of edge)" ...

Tips for enabling multiple v-list-group components to remain open simultaneously (bypassing the default Vue behavior)

Currently, I am facing an issue with Vue's default behavior where only one v-list-group can be open at a time. I am using Vuetify 2.6 and have attempted to use the multiple prop without success. Additionally, individually assigning the :value prop to ...

I am looking to showcase a series of icons linked together by connecting lines

I have successfully designed the layout and added icons, but I am facing difficulty in creating connecting lines between them. I attempted to utilize CSS borders and pseudo-elements, yet I cannot achieve the desired outcome. If anyone could offer a CSS-ba ...

When providing the index.html file using express.js, remember to include the user-agent header

When using my Express.js app to render my index.html page, I want to customize the http 'User-Agent' header. </p> <p>I've tried this method without success:</p> <pre><code>req.headers['user-agent'] = ...

Ways to incorporate a custom JavaScript function that is activated by an external server system?

I'm currently exploring a JavaScript widget that needs to operate within specific constraints: The widget initiates a request to a third-party server using a callback URL The third-party server pings the callback URL after a set period, triggering a ...

Assign an array value to the input based on the selection made in Javascript

After finding a lot of useful information in previous questions, I am struggling to get the last piece that I need. My PHP code helps me loop through form fields and assign unique names to each field using a variable. One of these fields is for "expense ty ...

Guide to illustrating the connections between origin and destination nations utilizing IP addresses

My goal is to create an interactive world map with a clickable marker for each country. When a user clicks on a source country's marker, I want to display interactions with other countries in an aggregated manner. While I have successfully drawn the w ...

Utilize Google Maps to receive directions to a specific destination and discover current traffic conditions along the route using their comprehensive API

I am currently working on implementing the Google Maps direction identifier functionality with traffic details. However, I am able to obtain directions but not specific traffic details for a selected path; it seems to apply overall traffic data instead. ...

Struggling with the proper state updating in React hooks when using dynamic naming conventions?

I am currently working with a react component that handles login requests to my server. This component is housed within a modal using Material UI. <TextField onChange={handleChange} autoFocus name="email" ...

Show a Toast in React without relying on useEffect to manage the state

I have successfully implemented the Toast functionality from react-bootstrap into my application using the provided code. However, I am unsure if it is necessary to utilize useEffect to set show with setShow(items.length > 0);. Would it be simpler to ...

Issue with PrimeNG Carousel width on mobile devices

Currently, I am in the process of developing a system interface with Angular and PrimeNG specifically for mobile devices. The main requirement is to have a carousel to display a set of cards. After evaluating different options, I decided to utilize the ngP ...

Sending information from a rails controller to a react component

Wondering how to pass the example @post = Post.all from the controller to React component props while integrating Rails with React via Webpacker. Is it necessary to do this through an API or is there another way? ...