Loop through divs and toggle class if another class exists - javascript

I want to toggle a class (and make the text blink using an interval) if another class is found.
CSS
.flash {
color: #DC4900;
}
HTML
<div class="text notify">Flash</div>
<div class="text notify">Flash</div>
<div class="text">Don't Flash</div>
JS
$(document).ready(function() {
$('.text').each(function() {
if ($(this).hasClass('notify')) {
setInterval(function() {
$(this).toggleClass('flash');
}, 1000);
}
});
});
https://jsfiddle.net/s9dv9qv3/
The interval is not working like it should. It should be adding the flash class to the text if the class notify is found.

Inside of the setInterval callback, this refers to the window object.
To work around this, one option is to capture a reference to this outside of the callback:
Example Here
$('.text.notify').each(function() {
var self = this;
setInterval(function() {
$(self).toggleClass('flash');
}, 1000);
});
I also removed the check for the notify class since you can just select those elements directly.
Alternatively, you could also just use the .bind() method to set the value of this:
Example Here
$('.text.notify').each(function() {
setInterval(function() {
$(this).toggleClass('flash');
}.bind(this), 1000);
});
As a side note, since you're toggling the class on all of the elements at the same time, you could just use:
setInterval(function() {
$('.text.notify').toggleClass('flash');
}, 1000);
If you want to set a delay, you could add a timeout based on the current index of the .each() loop:
Example Here
$('.text.notify').each(function(i) {
var self = this;
setTimeout(function() {
setInterval(function() {
$(self).toggleClass('flash');
}, 1000);
}, 1000 * i);
});
Lastly, you can avoid jQuery completely and use an infinite CSS3 animation:
.text.notify {
animation: flash 2s infinite;
}
#keyframes flash {
0%, 49% {
color: #000;
}
50%, 100% {
color: #DC4900;
}
}
<div class="text notify">Flash</div>
<div class="text notify">Flash</div>
<div class="text notify">Flash</div>
<div class="text notify">Flash</div>
<div class="text">Don't Flash</div>

You can do it using following code and css3 easily:
$( document ).ready(function() {
$('.text.notify').toggleClass('flash');
});
Css3:
.flash {
color: #DC4900;
animation: flasher 1s linear 2;
}
#keyframes flasher {
50% { opacity: 0.0; }
}
Check the fiddle here.

Do this instead:
$( document ).ready(function() {
setInterval(function() {
$('.text').each(function(){
if($(this).hasClass('notify')) {
$(this).toggleClass('flash');
}
});
}, 1000);
});

Hey I had the same problem, a have a list of divs all with .d-unit class, some I would set to available others would be pending or sold.
so this function will highlight all the d.units that also have the class of available. If yes then it will add or a remove the 'available-green' class which is how I change the highlight color.
*watch to see if you need period or not when putting in a class name.
function unitd() {
$(".unitd").toggleClass('filter-on');
$('.d-unit.available').each(function() {
var self = this;
if($(self).hasClass('available')) {
$(self).toggleClass( "available-green' );
}
})
}

Related

How long do I need to wait (setTimeout) to affect a class change when adding something to the DOM?

Here's the scenario... I add an element to the DOM that has an initial class to have 0 opacity and then add a class to trigger the opacity transition to 1 - a nice simple fade-in. Here's what the code might look like:
.test {
opacity: 0;
transition: opacity .5s;
}
.test.show {
opacity: 1;
}
const el = document.createElement('div')
el.textContent = 'Hello world!'
el.className = 'test' // <div class="test">Hello world!</div>
document.body.appendChild(el)
Now to trigger the fade-in, I can simply add the show class to the element:
setTimeout(() => {
el.classList.add('show')
}, 10)
I'm using a setTimeout because if I don't, the class will be added immediately and no fade-in will occur. It will just be initially visible on the screen with no transition. Because of this, I've historically used 10ms for the setTimeout and that's worked... until now. I've run into a scenario where I needed to up it to 20ms. This feels dirty. Does anyone know if there's a safe time to use? Am I missing something about how the DOM works here? I know I need to give the browser time to figure out layout and painting (hence the setTimeout), but how much time? I appreciate any insight!
Note: It looks like you can avoid adding a second class to fade in the element and thus avoid this timing problem, see silencedogood's answer. If that works for you, it seems like a much better approach than the below.
If for some reason that won't work for you, read on... :-)
I don't think there's any reasonable, safe setTimeout value you could use.
Instead, I'd use requestAnimationFrame just after appending the element. In my experiments, you need to wait until the second animation frame, so crudely:
requestAnimationFrame(function() {
requestAnimationFrame(function() {
el.classList.add("show");
});
});
Live Example:
document.getElementById("btn").addEventListener("click", function() {
const el = document.createElement('div');
el.textContent = 'Hello world!';
el.className = 'test';
document.body.appendChild(el);
requestAnimationFrame(function() {
requestAnimationFrame(function() {
el.classList.add("show");
});
});
});
.test {
opacity: 0;
transition: opacity .5s;
}
.test.show {
opacity: 1;
}
<input type="button" id="btn" value="Click me">
My logic for this is that when you've seen the first animation frame, you know that the DOM has been rendered with your element in it. So adding the class on the second animation frame should logically be after it's already been rendered without it, and so trigger the transition.
If I do it in the first animation frame, it doesn't work reliably for me on Firefox:
document.getElementById("btn").addEventListener("click", function() {
const el = document.createElement('div');
el.textContent = 'Hello world!';
el.className = 'test';
document.body.appendChild(el);
requestAnimationFrame(function() {
//requestAnimationFrame(function() {
el.classList.add("show");
//});
});
});
.test {
opacity: 0;
transition: opacity .5s;
}
.test.show {
opacity: 1;
}
<input type="button" id="btn" value="Click me">
...and that kind of makes sense to me, since it would be adding the class before the first time the element is rendered. (It did work if I added the element on page load, but not when I introduced the button above.)
You could use a pure css solution, since you're concerned with javascript being potentially 'dirty' due to the arbitrary delay:
#keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
.test {
opacity: 0;
animation: fadein 1s;
animation-fill-mode: forwards;
}
Live Example:
document.getElementById("btn").addEventListener("click", function() {
const el = document.createElement('div');
el.textContent = 'Hello world!';
el.className = 'test';
document.body.appendChild(el);
});
#keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
.test {
opacity: 0;
animation: fadein 1s;
animation-fill-mode: forwards;
}
<input type="button" id="btn" value="Click me">

How to run the same function for different IDs separately

I'm trying to animate multiple progress/loading bars. The code below works how I want it to and I could just copy and paste then replace the ID name but that seems unnecessarily long. What's an efficient way to use my jquery code with different types of IDs (eg: #bar-x, #bar-y, #bar-z)? I've tried separating with commas like below
$(function() {
var $stat = $('#bar-x, #bar-y, #bar-z');
$stat.waypoint(function() {
$stat.css({
animation: "loading 1s linear",
opacity: "1"
});
}, { offset: '95%' });
});
but then it runs all the animations at the same time when the first object reaches the requirement instead of when each individual object reaches the said requirement.
You could always just wrap it in a function and use it like this
function myAnimation(selector){
selector.waypoint(function() {
selector.css({
animation: "loading 1s linear",
opacity: "1"
});
}, { offset: '95%' });
}
myAnimation($('#bar-y'));
This way you'd be able to call on your animation with any kind of selector repeatedly and re-usable fashion.
I'd suggest reading up on some functional programming.
After looking at Waypoints, it seems your issue comes from using the $stat object inside your callback function:
...
$stat.css({
animation: "loading 1s linear",
opacity: "1"
});
...
When the requirement for one of the waypoints is reached, it will call your .css animation on ALL of the elements that $stat contains. What you need is to make it a bit more dynamic and use this.element in place of $stat (or similar, depending on the version of Waypoints you're using).
It seems that the other answers aren't acknowledging the queue-ing aspect of your question so I figured I'd touch on that. In such a case, you could loop through the elements and attach the animation one second apart to each one.
Using the code below, all of the setTimeout()s will be instantiated at the same time, however the first will be for 0ms, the second 1000ms, the third 2000ms, so on and so forth.
var $stat = $('.bar'); //add class "bar" to each item you want to be included
var msDelay = 1000;
$stat.each(function(index) {
setTimeout(() => loadNext($(this)), msDelay * index);
});
function loadNext($elem) {
$elem.waypoint(function() {
$elem.css({
animation: "loading 1s linear",
opacity: "1"
});
}, {
offset: '95%'
});
};
Demo
var $stat = $('.bar');
var msDelay = 1000;
$stat.each(function(index) {
setTimeout(() => loadNext($(this)), msDelay * index);
});
function loadNext($elem) {
$elem.css({ opacity: "1" });
};
.bar {
opacity: 0;
padding: 20px;
margin: 5px;
background-color: red;
transition: opacity 1s;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
var divId = "#bar-x";
function animate (var divId)
{
var $stat = $(divId);
$stat.waypoint(function() {
$stat.css({
animation: "loading 1s linear",
opacity: "1"
});
}, { offset: '95%' });
}

Play CSS animation only when AJAX query returns a certain result

I have a div showing an alert count. Currently the div is updated via a jQuery script refreshing it every five seconds and displaying the count. If the count is zero the div displays the message “Currently no new alerts” and displays in green, while if there are 1 or more alerts the div is displayed in red and the number of alerts is displayed.
I currently have something like this:
<script>
setInterval(function() {
$("#alertWrap").load(location.href + "#alertWrap>*","");
}, 5000);
</script>
<div class="alertWrap" id="alertWrap">
<?php
$alertcountCheckQ = mysql_query("SELECT COUNT(alert_status)numerOfAlerts FROM alerts WHERE alert_status = 'unacknowledged'");
$alertcountCheckR = mysql_fetch_object($alertcountCheckQ);
if ($alertcountCheckR->numerOfAlerts >= 1) {
echo "<div class=\"alertContentContainer redBackground\">$alertcountCheckR->numerOfAlerts new alert(s)</div>";
} else {
echo "<div class=\"alertContentContainer greenBackground\">Currently no new alerts</div>";
}
?>
</div>
I would like to apply a CSS animation to the content of the div when there’s a new alert – a flash perhaps to grab people’s attention – however, I can’t figure out how this would be done without the animation firing every time the div is refreshed.
You can do something like this with css3 animations (wait 2 seconds for the effect). You just need to call the function which add the class .pulse and remove it after 4 seconds:
Note I show you the pulse effect only, not the logic because I assome that you handle with it. The demo show you only what you do after you know that you need to take the user's attention.
setTimeout(function() {
pulse(1);
}, 2000);
function pulse(counter) {
$('#total').html(counter);
$('.alerts').addClass('pulse');
setTimeout(function() {
$('.alerts').removeClass('pulse');
}, 4000);
}
.alerts {
margin:50px;
display:inline-block;
}
.pulse {
outline: rgba(191, 28, 86, 1) solid 2px;
animation:pulse 1s ease infinite;
}
#keyframes pulse {
0% {
outline-offset:0;
opacity:1;
}
100% {
outline-offset:10px;
outline-color:rgba(191, 28, 86, 0)
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<span class="alerts">
<span id="total">0</span> alerts
</span>
You could supply a callback function as the third argument to .load(). That function would be run after the new contents are attempted to be loaded from the server. In that function, if new alert status was successfully loaded, check the contents that were loaded using the first parameter, responseText. If the contents have a new alert, run the animation.
The following example code doesn’t use CSS animation, but rather the jQuery functions fadeOut and fadeIn to flash twice. But you can customize flashElement to do whatever you want, such as adding a class to that element, where there is already CSS to make elements with that class flash.
setInterval(function() {
$("#alertWrap").load(location.href+"#alertWrap>*", "", function(responseText, textStatus) {
if (textStatus === "success" && responseContainsNewAlerts(responseText)) {
flashElement($("#alertWrap"));
}
});
}, 5000);
function responseContainsNewAlerts(responseText) {
return responseText.indexOf("redBackground") !== -1;
}
function flashElement($element) {
var flashDelayMs = 200;
var numFlashes = 2;
for (var i=0; i < numFlashes; i++) {
$element.fadeOut(flashDelayMs).fadeIn(flashDelayMs);
}
}
you could add Jquery code that runs when the alerts are refreshed and changes the CSS of the alerts to animate if there is a new alert, and to remove the CSS animation if there isn't one.
if (thereIsANewAlert){
$(".alertContentContainer redBackground").css("animation-name","example");
$(".alertContentContainer redBackground").css("animation-duration","4s");
}else{
$(".alertContentContainer redBackground").css("animation-name","");
$(".alertContentContainer redBackground").css("animation-duration","");
}
However i would recommend simply animating Via Jquery like this example:
if (thereIsANewAlert){
$(".alertContentContainer redBackground").animate({left: '100px'});
}

Don't ng-show element until ng-hide CSS transition is complete?

Simple question, but I'm having implementation troubles. If I have the following DOM setup:
<h1 class="fade" ng-repeat="child in parent.children" ng-show="parent.activeChild== child ">#{{ child.title }}</h1>
When the activeChild property of the parent model changes, how can I fade out the currently active child, before the model changes, and then fade in the newly active child post-change.
I have it working roughly, with just CSS transitions using this:
.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;
}
}
But, this ends up producing this problem (Plunkr):
Essentially, I want to chain my animation. I've tried reading the ng-animate docs, but I'm having trouble the syntax necessary to deliver the effect I want.
I've seen the Angular docs have 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 I want to apply while fading in/out? The class I'm expecting?
What is doneFn meant to be? I assume it's a function that's run once the animation is complete? What goes in there?
What do I do in the addClass and removeClass function then, if I already have a doneFn?
The Goal
I'd like to generate a working animation directly using Angular's ngAnimate module, with either CSS or JS. How can I achieve this?
Why do you use a separate <h1> for each heading. You can use a single <h1> tag to show your heading.
I have created a demo for your problem and I have successfully done your requirement.
Updated
Note, codes are edited to use ngAnimate module. When you use ngAnimate module, it will create a class .ng-hide when you hide an element,
Here is the controller for your app,
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);
}
}]);
And your html page should be look like this,
<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>
And I have used CSS3 to implement the fade in and fade out animation,
.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 achieve your requirement, I have used ng-class and $timeout in angularJS.
You can see that, I have only one <h1> tag to display your heading. When I change the heading I just change it's binding property $scope.parent.activeChild.
And I have used two scope variables $scope.heading.fadeOutModel and $scope.heading.fadeInModel to add and remove classes fadeIn and fadeOut dynamically.
When user clicks to change the heading, I have added the class fadeOut to your heading. So, this will show an animation of fade out. And also I have fired a function in app.js, changeHeading().
You can see that, I forced the angular to wait for 1000 milliseconds to finish fade out animation. After this time, it will replace the selected heading to new one and add a class fadeIn. So, it will start animation for fade in.
Hope this will help you !!!
A more ng-animate way to show a specific element depending on a selection would be to use ngSwitch. This directive is used to conditionally swap DOM structure on your template based on a scope expression. Here is a example.
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. Also here is a example of it on angular's website.
You can use .animation to define animations that are Javascript based. For example, the functions you define as the values of addClass and removeClass
app.animation('.fade', [function() {
return {
addClass: function(element, className, doneFn) {
},
removeClass: function(element, className, doneFn) {
}
};
}]);
are called by Angular when it detects that you are adding or removing a class from an element, from one of the methods:
{{ }} interpoation in a template. E.g. <span class="{{shouldFade ? 'fade' : ''}}">....
Using ng-class in a template. E.g. <span ng-class="{fade: shouldFade}">...
Using the $animate service in a directive. E.g. $animate.addClass(element, 'fade') or $animate.removeClass(element, 'fade')
What is className? Is it the class I want to apply while fading in/out? The class I'm expecting?
In this example it will be fade. It a bit strange admittedly as in the example it is already clear this is the class name involved. However, if in the same digest cycle you're adding multiple classes to the same element, then the concatenation of them are passed as this string.
What is doneFn meant to be? I assume it's a function that's run once the animation is complete? What goes in there?
It's a function that you call once whatever Javascript animation you define is done. For example, to define an animation that does nothing as all:
addClass: function(element, className, doneFn) {
doneFn();
},
Calling it tells Angular that the animation has complete. This will, among other things, remove the ng-animate class from the element.
What do I do in the addClass and removeClass function then, if I already have a doneFn?
You put in them some code, perhaps using timeouts or a 3rd party library, to change the element somehow. When you have finished, you call doneFn. For example, a 1 step opacity "animation":
addClass: function(element, className, doneFn) {
element.css('opacity', 0.5);
setTimeout(function() {
doneFn();
}, 1000);
},
I'd like to generate a working animation directly using Angular's ngAnimate module, with either CSS or JS.
This doesn't really have much to do with the answers above! If I were doing a real-case, I strongly suspect I would position the elements absolutely, as anything else (that I can think of) at least, is a bit overly complicated.
However, if you do really want to chain the animations using ngAnimate, one possible way is to use the fact that $animate.addClass and $animate.removeClass returns a promise when it completes. In order to chain onto the end of such a promise returned when hiding an element, it must be called from some sort of central location, and keep track of which element is visible, being hidden, and being shown.
A way of doing this is to use 2 custom directives. One will be on each element to show and hide, that could be used very much like ngShow. The other will be a parent directive that will allow only one element to be visible at any time, and chain removal of the ng-hide class (and associated animations) after any addition of ng-hide. The directives will have to communicate, could be called something like ngShowUnique and ngShowUniqueController, such as in the following example.
<div ng-show-unique-controller>
<h1 class="fade" ng-repeat="child in parent.children" ng-show-unique="parent.activeChild == child">#{{child.title}}</h1>
</div>
and they could be implemented as below.
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);
});
}
};
});
This can be seen at http://plnkr.co/edit/1eJUou4UaH6bnAN0nJn7?p=preview . I have to admit, it's all a bit faffy.
using ngRepeat that shows only one element at time, in my opinion, is a bad idea... because you're showing only one element!
you can use the parent.activeChild property directly...
Have a look on what follows:
Note: I did this snippet in just ten minutes, it's unoptimized and can have some bug... you can use it as starter :)
(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>
The problem is that H1 is a block level element that is positioned within it's parent and no overlap is allowed. That is why you see one animation that's disappearing pushing down the animation that is appearing.
You can see that this is happening more clearly here: Demo
To fix this, you want to keep the block level element H1, and make its position relative, so that it can keep its relative position in the overall flow of the page. Then set the child SPAN elements to have absolute positioning - absolute position relative to the parent H1. This allows all span elements to overlap each other.
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>
There is one problem though... Since the SPAN elements have absolute positioning, it is removed from flow when animating, and the parent H1 can't resize to fit the SPAN contents. This causes the SPAN to jump unexpectedly.
The way to address this (and admittedly, it's a bit of a hack) is by adding an empty space after the SPAN repeater. So when the ngRepeat SPANS get pulled out of normal flow because of absolute positioning, the empty space which is outside the ngRepeat preserves the spacing of the H1.
Here is a working Plunker.
You might want to look into transitionend event which is supported by all modern browsers.
element.addEventListener('transitionend', callback, false);
Quick answer to this - To solve this problem in the past I have always positioned the content absolute. This way when the transition takes place it stays in the same position.
There is no other way around it because the content takes up space in the dom if its inline or inline-block which is why you see the jump until the transition is finished

Angular way to do an Element Directive animation when Object is deleted?

I'm rendering an scoped Array of Objects(payments in this case), and passing each one to a payment Directive like this:
<div id="payable" ng-controller="PaymentsController">
<payment ng-repeat="payment in payments" data="payment" class="payment"></payment>
</div>
This works really well! So when I delete an element from the scoped Array from the controller like this:
app.controller('PaymentsController', function($scope) {
//The Payments Array(each object passed to a Directive)
$scope.payments = [ { id: 1, amount: 10 }, { id: 2, amount: 15 } ];
$scope.deletePayment = function(index) {
//This deletes the Array Element and removes associated
//Directive template from the DOM
$scope.payments.splice(index, 1);
}
});
The CSS (uses compass mixins for simplicity)
.payment.ng-enter {
#include transition(all 2s ease-out);
opacity: 0;
}
.payment.ng-enter-active {
opacity: 1;
}
.payment.ng-leave {
#include transition(all 2s ease-out);
}
.payment.ng-leave-active {
opacity: 0;
}
Again, the above works as expected, I delete an element from the payments Array and the directive-template/view corresponding to the deleted Array element is removed from the DOM, This is PERFECT, except for the fact it's removed instantly!
EDIT:
The reason the animations like fadeOut don't work and the result is that the ( < payment > ) is removed instantly(after a specified time in the CSS) is that the animation is acting over the ( < payment >) custom tag, which is just a wrapper for the actual element.
Directive JS definition:
(function() {
var app = angular.module('paymentDirectives', []);
app.directive('payment', function() {
return {
restrict: 'E',
scope: {
payment: '=data'
},
templateUrl: 'partials/payment.html'
};
});
})();
The animation should act on the template referenced/wrapped by the directive custom tag( < payment > )
partials/payment.html
<div class="a-payment">
<div class="content">
<p>
<label>{{payment.amount}}</label>
</p>
</div>
</div>
In this case it would be the div with class="a-payment" of course and when the animation is complete it should then remove the payment tag element
What is the Angular way(for the latest version) to do an animation for this case(ie. Element Directive is removed from the DOM)?
Thank you very much in advance, and let me know if you need more from the code I'm using.
This is likely to do with the fact that most custom tags, such as your <payment>, are display: inline; by default.
You should set their style to be display: block in the CSS/SASS.
You can do this in many ways, for example, you can create a class that will trigger the CSS animation, and before deleting the object, you first assign it that class. Here's how:
var deleteAnimDuration = 1000; // let's use one second for our example
$scope.deletePayment = function(index) {
//This deletes the Array Element and removes associated
//Directive template from the DOM
$scope.payments[index].deleteAnim = true; // or whatever property makes sense to you
$timeout(function(){
$scope.payments.splice(index, 1);
}, deleteAnimDuration);
}
Then on the directive, you can use ng-class:
<payment
ng-repeat="payment in payments"
data="payment"
ng-class="{deleting: payment.deleteAnim}">
</payment>
Then in the CSS:
payment.deleting {
transition: opacity 1s linear; // again, one second
opacity: 0;
}
Since this sample animation (opacity fade) will run for one second, you need to set deleteAnimDuration for the $timeout to one second (1000 in milliseconds).
So, what happens:
you click delete on a payment
it sets payment.deleteAnim to true, which assigns the deleting class to the element
the timeout for the animation duration is set
animation starts
animation ends
element removed from the DOM
This is the concept from the DOM standpoint:
var divs = document.getElementsByTagName('div');
for (var i = 0; i < divs.length; i++) {
divs[i].addEventListener('click', function(e) {
e.target.className = 'deleting';
deleteEl(this)
})
}
function deleteEl(el) {
setTimeout(function() {
el.parentElement.removeChild(el);
}, 1000);
}
.deleting {
transition: opacity 1s linear;
opacity: 0;
}
div {
width: 50px;
height: 50px;
display: inline-block;
background: #eee;
}
<div>Click me</div>
<div>Click me</div>
<div>Click me</div>
<div>Click me</div>
Of course, this can work with JS animations as well.
Load angular-animate.min.js in your HTML.
Add ngAnimate into your module dependencies.
Add a class to your payment directive element: e.g. <payment class="my-animation" ...></payment>.
Add the following CSS (referencing your class in step 3):
.my-animation.ng-leave { opacity: 1; transition: opacity 300ms linear; }
.my-animation.ng-leave.ng-leave-active { opacity: 0; transition: opacity 300ms linear; }
Celebrate

Categories

Resources