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

Creating a unique navigation route in React

My application has a consistent layout for all routes except one, which will be completely different from the rest. The entire application will include a menu, body, footer, etc. However, the one-off route should be standalone without these elements. How ...

I am facing unexpected results while trying to search for a string array object using elemMatch in Mongoose

I am encountering an issue that I can't seem to resolve. I need to utilize a query search to match the data of one job with user data, but I'm facing some obstacles. The primary challenge is within my query search for the job where the data looks ...

In Javascript, an error occurs when something is undefined

I've been grappling with a Javascript issue and seem to have hit a roadblock. In Firefox's console, I keep encountering an error message that says "info[last] is undefined," and it's leaving me puzzled. The problematic line appears to be nu ...

Node server quickly sends a response to an asynchronous client request

Apologies for my lack of writing skills when I first wake up, let me make some revisions. I am utilizing expressjs with passportjs (local strategy) to handle my server and connect-busboy for file uploads. I believe passport will not have a role in this pr ...

What is the best way to include a &nbsp; in a map function using JavaScript?

A challenge I encountered while working in React is as follows: <Grid item> { rolePriorities.map((rp, index) => ( <Chip key={index} label={rp} color="primary" sx={{ color: "whitesmoke" }} /> ...

`Is it possible to retrieve Wikipedia information directly from a Wikipedia link?`

Is there a way to create a feature where users can input a Wikipedia page link and retrieve all the text from that page? I am working on adding a functionality to my website where users can paste a link to a Wikipedia page they want to analyze into an inp ...

Using AngularJS to bind models to a multidimensional array

As I am new to angular js, please bear with me. In my view, I have a grid of text input boxes that I would like to map to a 2D array in my controller or something similar in java script. The code in my view is as follows: <div ng-repeat="row in [1,2,3, ...

react-datepicker displaying error message "Preventing default action not possible within a passive event listener invocation"

I've integrated the react-datepicker library in portal mode. While it functions well on browsers, I encounter an error when using mobile browser mode. An issue arises stating: "Unable to preventDefault inside passive event listener invocation" Des ...

Challenges with the efficiency of the Material UI Datagrid

I am currently using MUI Datagrid to display my records, but I am experiencing delays with my modal and drawer components. Even after attempting to optimize with useMemo for my columns, I have not been able to achieve satisfactory performance. https://i.st ...

Utilizing Express.js for reverse proxying a variety of web applications and their associated assets

I am looking to enable an authenticated client in Express to access other web applications running on the server but on different ports. For instance, I have express running on http://myDomain and another application running on port 9000. My goal is to re ...

Using a combination of ajax and php to enhance the voting system

Thank you for taking the time to read this. I am currently working on improving my skills, so I decided to embark on a project to enhance my knowledge. I have created a basic voting system where each content is displayed using PHP and includes an up or do ...

Modify the h:outputText value dynamically with the power of jQuery!

Is it possible to use jQuery to dynamically change the value of my OutputText component? This is the code snippet for my component: <h:outputText id="txt_pay_days" value="0" binding="#{Attendance_Calculation.txt_pay_days}"/> I would apprecia ...

Unable to establish connection between Router.post and AJAX

I am currently in the process of developing an application to convert temperatures. I have implemented a POST call in my temp.js class, which should trigger my ajax.js class to handle the data and perform calculations needed to generate the desired output. ...

What could be causing my Google Chrome extension to only allow me to open 25 tabs instead of more?

Having a frustrating issue with my code that is causing problems when used as a Chrome Extension. Despite checking and confirming that the code should work correctly, it seems to stop opening pages after tab 25. I have verified that the code attempts to o ...

Utilizing React JS to Develop Cell Components for a Schedule, Ensuring Teams are Unique within the Same Row

I am currently facing a challenge involving a table that has a fixed number of timeslots (y axis) and pitches (x axis). In addition, I have a collection of match objects with the following structure: Object { id: 3, teamA: "pelt", teamB: "Ranelagh" } Eac ...

Handling Firebase callbacks once the save operation is completed

Currently using AngularFire with Firebase. Unfortunately, still stuck on Angular 1 :-( I'm curious if there's a method to set up a callback function that triggers every time data is successfully saved in the database. I am aware of the option to ...

The ng-disabled directive in AngularJS fails to disable the button as expected

I'm having an issue with setting an input button to disabled when a user selects a specific value from a select box: Here is my select menu: <select id="jobstatus" name="jobstatus" ng-model="associate.JobStatus" class="form-control"> & ...

Having trouble with the Wordpress JS CSS combination not functioning properly and unable to determine the cause

I recently set up a page on my WordPress site using the Twenty Seventeen theme. At the top of the page, I created a toolbar for users to easily adjust the font size and background color. Check out the image below for reference: See Image for Bar Here&apo ...

ParcelJS takes a unique approach by not bundling imported JavaScript libraries

My NodeJS app, which is a Cloudflare Worker, seems to be having trouble with bundling the 'ping-monitor' dependency. In my main typescript file (index.ts), I import the handler module and the first line reads: const Monitor = import('ping-m ...

Unable to access Vue.js cookies: they are not defined

I integrated vue-cookies into my project successfully. In the main.js file, I added the following code: createApp(App) .use(store) .use(router, axios, VueCookies) The script section in App.vue file looks like this: <script> import Navbar fr ...