I have a Vue component that is an image slider, and I use transform: translateX to cycle through the images. I based my work off of Luis Velasquez's tutorial here: https://dev.to/luvejo/how-to-build-a-carousel-from-scratch-using-vue-js-4ki0
(needed to make a couple of adjustments, but it follows it pretty closely)
All works fairly well, except I get a strange flicker after the sliding animation completes, and I cannot figure out why.
In my component I have:
<button
:class="btnClass"
#click="prev"
#keyup.enter="prev"
#keyup.space="prev"/>
<button
:class="btnClass"
#click="next"
#keyup.enter="next"
#keyup.space="next" />
<div class="slider-wrap">
<div class="slider-cards">
<div ref="slidewrap"
v-hammer:swipe.horizontal="handleSwipe"
class="cards"
>
<div class="inner" :style="innerStyles" ref="inner">
<SliderCard v-for="(item, index) in slides"
:key="`${item.id}-${index}`"
:item="item"
/>
</div>
</div>
</div>
</div>
The navigation of those is handled via the following methods (just including the "next" navigation one to spare some redundancy here:
next () {
// translate the inner div left, push first element to last index, and slide
// inner div back to original position
if (this.transitioning) {
return;
}
this.transitioning = true;
const elementToMoveToEnd = this.slides[2];
this.moveLeft();
this.afterTransition(() => {
this.resetTranslate();
this.slides.splice(0, 1);
this.slides.push(elementToMoveToEnd);
this.transitioning = false;
});
},
resetTranslate() {
/// NOTE: this.navigationStep is just a static px value set up elsewhere in my component, based on screenwidth -- it just sets how much the slider should translate based on the card widths.
this.innerStyles = {
transition: 'none',
transform: `translateX(-${this.navigationStep})`
};
},
moveLeft() {
this.innerStyles = {
transform: `translateX(-${this.navigationStep})
translateX(-${this.navigationStep})`
};
},
afterTransition (callback) {
const listener = () => {
// fire the callback (reorganizing slides, resetting the translation),
// and remove listener immediately after
callback();
this.$refs.inner.removeEventListener('transitionend', listener);
};
this.$refs.inner.addEventListener('transitionend', listener);
},
In my CSS, I control the translation animation via:
.inner {
transition: transform 0.2s linear;
white-space: nowrap;
-webkit-transform-style: preserve-3d;
}
(the preserve-3d was a desperate attempt to fix the flickering).
My slides move, but there is a flash immediately after they move -- I suspect it has something to do with competing translations/transitions in the moveLeft and resetTranslation methods, but I've messed with those, and nothing has improved the flashing. If anyone has any ideas, I'd really appreciate it!
So, i am trying to do an animation on my image in html/css. The problem is, it triggers when i load the page, but i want it to trigger when i scroll down to the image.
Here is my HTML part:
<figure>
<img src="Assets/Images/klenet.jpg" id="clenet_picture">
<figcaption id="clenet_text">Clenet Series 1 от 1979 година.</figcaption>
</figure>
Here is the CSS part:
#clenet_picture
{
. . .
animation-name: image-anim;
animation-duration: 4s;
}
#keyframes image-anim
{
from {opacity: 0%}
to {opacity: 100%}
}
I know i need to use some JS to make it work, but how exactly do i do that?
Add another class to it using JS. For example animate
Then you can use #clenet_picture.animate instead of #clenet_picture and animation will only start when you have applied new class
How to check if image is in viewport: How can I tell if a DOM element is visible in the current viewport?
I think i found a very simple solution.
However, there is one stupid problem.
Here is the code:
const checkpoint = 750;
window.addEventListener("scroll", () => {
const currentScroll = window.pageYOffset;
if (currentScroll <= checkpoint) {
opacity = 1 - currentScroll / checkpoint;
} else {
opacity = 0;
}
document.querySelector(".toAnimate").style.opacity = opacity;
});
The problem is that now it is doing the opposite... When i scroll to the picture, its opacity becomes 0 and when i scroll outside it, it becomes 1...
If someone has a solution, i will be thankful.
And thanks to all answers!
I fixed it!
Here is the working code:
//The checkpoint is set by you!
const checkpoint = 550;
window.addEventListener("scroll", () => {
const currentScroll = window.pageYOffset;
if (currentScroll <= checkpoint) {
opacity = 0 + currentScroll / checkpoint;
} else {
opacity = 1;
}
document.querySelector(".toAnimate").style.opacity = opacity;
});
This is the most simple solution, works without CSS!
I know this is fairly easy in jQuery, but I want to do this in plain 'ol "will be around forever" javascript.
I have a dropdown select on my page. I choose one of 8 options. There is a default image showing on the page. When I select an option, the image changes to that pic. It all works fine.
But I want to make the image change a fade out, fade in switch over because I, like most of you, can't leave well alone. We have to keep fiddling.
The javascript that I have, which is triggered by an onchange="setPicture()" on the select dropdown is:
function setPicture(){
var img = document.getElementById("mySelectTag");
var value = img.options[img.selectedIndex].value;
document.getElementById("myImageDiv").src = value;
}
This works fine. The value of the selected index is a string with the path for each image. I just want a fade out then fade in stuck in there somewhere. I have fiddled about a bit, calling another function before changing the src but no luck.
Can someone point me in the right direction?
The easier way would be to use css keyframes alone.
But from javascript there is the web animation api made for that.
Here is a quick modif from the example, to match your case.
function setPicture(){
alice.animate(
[
{ opacity: 1 },
{ opacity: .1},
{ opacity: 1 }
], {
duration: 3000,
iterations: Infinity
}
)
}
<button onclick="setPicture()">
OPACITY ANIMATION
</button>
<img id="alice"
src="https://mdn.mozillademos.org/files/13843/tumbling-alice_optimized.gif"
>
</img>
How about setting the image default CSS with the opacity of 0 and with transition time
then in JavaScript just add a class that will make the opacity set to 1
HTML:
<img class="img1" src="sampleimg.jpg">
CSS:
.img1 {
opacity: 0;
transition: all .3s;
}
.img1.show {
opacity: 1;
}
JS:
function setPicture() {
var img = document.querySelector('.img1');
img.src = 'urlofnewimage';
img.classList.add('show');
}
Hope this helps.
Juste one function for all :
function fadeOutEffect(target) {
var fadeTarget = document.getElementById(target);
fadeTarget.style.opacity = 1;
fadeTarget.style.transition = "opacity 2s";
fadeTarget.style.opacity = 0;
setTimeout(function(){
fadeTarget.style.display = "none";
}, 2000);;
}
I have a ReactJS project and I've been advised not to use jQuery for various reasons, so I'm attempting to convert the following jQuery to JavaScript -- it smoothly changes background color while scrolling the page:
$( window ).ready(function() {
var wHeight = $(window).height();
$('.slide')
.height(wHeight)
.scrollie({
scrollOffset : -50,
scrollingInView : function(elem) {
var bgColor = elem.data('background');
$('body').css('background-color', bgColor);
}
});
});
CSS:
* { box-sizing: border-box }
body {
font-family: 'Coming Soon', cursive;
transition: background 1s ease;
background: #3498db;
}
p {
color: #ecf0f1;
font-size: 2em;
text-align: center;
}
a {
text-decoration: none;
}
HTML:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/2542/jquery.scrollie.min_1.js"></script>
<div class="main-wrapper">
<div class="slide slide-one" data-background="#3498db">
<p>Title</p>
<center>Go To Green.</center>
</div>
<div class="slide slide-two" data-background="#27ae60">
<a name="green">
<p>Green area</p>
<center>Go To Red.</center>
</a>
</div>
<div class="slide slide-three" data-background="#e74c3c">
<a name="red">
<p>Red area</p>
<center>Page over. Hope that was helpful :)</center>
</a>
</div>
But how can I do the conversion to JavaScript to fit the ReactJS project?
Thank you in advance and will be sure to accept/upvote answer
Changing from JQuery to JavaScript is always possible. Because JQuery builds on JavaScript. Most of the time ( like in your case ) it's not even that much work.
I've not changed your CSS or HTML. So this is just some new JavaScript. However you should put this script at the end of your website.
(function() { // create an own scope and run when everything is loaded
// collect all the slides in this array and apply the correct height
var slides = document.getElementsByClassName('slide')
for (slide of slides) slide.style.height = window.innerHeight + 'px'
// use the native scroll event
document.addEventListener("scroll", function() {
// how much have we scrolled already
var currentOffset = document.documentElement.scrollTop || document.body.scrollTop
// now check for all slides if they are in view (only one will be)
for (slide of slides) {
// 200 is how much before the top the color should change
var top = slide.getBoundingClientRect().top + currentOffset - 200
var bottom = top + slide.offsetHeight
// check if the current slide is in view
if (currentOffset >= top && currentOffset <= bottom) {
// set the new color, the smooth transition comes from the CSS tag
// CSS: transition: background 1s ease;
document.body.style.background = slide.dataset.background
break
}
}
})
}())
Additionally you might want to listen on resize event, because as of now when you resize the window will look a bit off ( this replaces the 5 line of the code above)
function setSize() {
for (slide of slides) slide.style.height = window.innerHeight + 'px'
}
window.addEventListener("resize", setSize)
setSize()
Solution
(function() {
var slides = document.getElementsByClassName('slide')
function setSize() {
for (slide of slides) slide.style.height = window.innerHeight + 'px'
}
window.addEventListener("resize", setSize)
setSize()
document.addEventListener("scroll", function() {
var currentOffset = document.documentElement.scrollTop || document.body.scrollTop
for (slide of slides) {
// 100 is how much before the top the color should change
var top = slide.getBoundingClientRect().top + currentOffset - 100
var bottom = top + slide.offsetHeight
if (currentOffset >= top && currentOffset <= bottom) {
document.body.style.background = slide.dataset.background
break
}
}
})
}())
body {
font-family: 'Coming Soon', cursive;
transition: background 1s ease;
background: #3498db;
}
p {
color: #ecf0f1;
font-size: 2em;
text-align: center;
}
a { text-decoration: none;
}
<div class="main-wrapper">
<div class="slide slide-one" data-background="#3498db">
<p>Title</p>
<center>Go To Green.</center>
</div>
<div class="slide slide-two" data-background="#27ae60">
<a name="green">
<p>Green area</p>
<center>Go To Red.</center>
</a>
</div>
<div class="slide slide-three" data-background="#e74c3c">
<a name="red">
<p>Red area</p>
<center>Page over. Hope that was helpful :)</center>
</a>
</div>
The plugin you are referring to links an example from their README that includes a link to a newer version that makes use of this other plugin that does not use jQuery and in fact does what you want it to do, I think. It is called in-view and it looks very good to me (both the functionality and the code).
Its usage is very similar to what you are doing currently. From the newer example linked above:
var $target = $('.wrapper');
inView('.section').on('enter', function(el){
var color = $(el).attr('data-background-color');
$target.css('background-color', color );
});
I do realize that I am in a way not answering the question, because this is not a jQuery-to-vanilla-JS guide, but I do think that it helps to know that somebody already did it for you.
Should give you some idea.
var wHeight = window.innerHeight;
To select elements in javascript:
on browser open inspect element, console tab and type:
document.get
and it gives you hint what to get
To get style of element:
var elem1 = document.getElementById("elemId");
var style = window.getComputedStyle(elem1, null);
To set Property:
document.body.style.setProperty(height`, wHeight +"px");
Below is some modifications that you can make, As people commented I am not trying to teach you how to's but want to give you some start:
// $( window ).ready(function() { //remove this
var wHeight = $(window).height(); // USe this instead: var wHeight = window.innerHeight;
$('.slide') //instead of selecting with jquery "$('.slide')" select with: Javascript var x = document.getElementsByClassName("example") ;
.height(wHeight) // Here you are chaining methods, read this article https://schier.co/blog/2013/11/14/method-chaining-in-javascript.html
.scrollie({
scrollOffset : -50,
scrollingInView : function(elem) {
var bgColor = elem.data('background'); //var bgColor = window.getComputedStyle(elem, null);
$('body').css('background-color', bgColor); //document.body.style.background = bgColor;
}
});
// }); //remove this
For .... in
var t = document.getElementsByClassName('slide')
console.log(typeof t) //object
for (var prop in t) {
console.log('t.' + prop, '=', t[prop]);
// output is index.html:33 t.0 = <div class="slide slide-one" data- background="#3498db">…</div>
// output is index.html:33 t.1 = <div class="slide slide-two" data- background="#27ae60">…</div>
// output is index.html:33 t.2 = <div class="slide slide-three" data-background="#e74c3c">…</div><a name="red">…</a></div>
// output is index.html:33 t.length = 3
// output is index.html:33 t.item = function item() { [native code] }
// output is index.html:33 t.namedItem = function namedItem() { [native code] }
}
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