To animate state changes in angular.js ui-router there are classes for enter and on leave. It means that when I make an animation it will always the same for every view.
This example is not mine, but notice that when I go back to a previous state the animation is the same, it goes left. http://embed.plnkr.co/M03tYgtfqNH09U4x5pHC/preview
Is there a way to add animation per state or on other events?
As the animations are applied through CSS it seems your only option is to determine what CSS should be applied based on the new state and previous state. It is easy to apply a class to the outgoing view on view change events, but much more difficult for the incoming view.
This should work but involves hacking angular-ui-router.js. I think it should be relatively straight forward to write this functionality into an extension script of some kind.
Create different CSS classes for the different animations that will be performed. E.g. slide-left and slide-right as shown below.
In the forms controller add a scope variable that indicates which animation should take place e.g. $scope.animationType.
In the forms controller add a listener to the view change event that will change the value of $scope.animationType depending on the current and future states. The value should match the CSS class with the animation you want to perform.
In angular-ui-router.js find the cleanupLastView function and add the parameter and the first first two lines:
.
function cleanupLastView(clonedElement) {
if(clonedElement) clonedElement.attr("class", scope.animationType);
if(currentEl) currentEl.attr("class", scope.animationType);
...
Then you have to change the call to cleanupLastView so the cloned element is included: cleanupLastView(clone)
Abbreviated CSS with #keyframes not included.
.form-view-slide-left.ng-enter {
animation:slideInRight 0.5s both ease;
}
.form-view-slide-left.ng-leave {
animation:slideOutLeft 0.5s both ease;
}
.form-view-slide-right.ng-enter {
animation:slideInLeft 0.5s both ease;
}
.form-view-slide-right.ng-leave {
animation:slideOutRight 0.5s both ease;
}
Related
I'm using slideUp and slideDown to animate sections hiding and showing using AngularJS's ngShow. It works fine, but I'd much rather have slideLeft and slideRight. How would I go about recreating slideUp and slideDown for those?
slideUp automatically hides the the element and slideDown automatically shows it - how would I be able to configure this such that they hide and show when I want then to? e.g.:
$(element).slideLeftAndHide();
$(element).slideLeftAndShow();
As opposed to
$(element).slideUp(); // $element.slideUpAndHide();
You can use the following to achieve this:
$('#element').show("slide", { direction: "left" }, "fast");
Since you tagged Angular.js, I assume you're also using Angular. You should prefer using something like ng-class instead of literally showing and hiding elements with jQuery. This is a good, modular way to do what you want using existing Angular.js capabilities and fast CSS animations.
I also assume that you're doing the show/hide part in response to some sort of conditional value changing, is that right?
If so, to start off:
1. When the conditional value changes, let the DOM know by adding a class name when a condition turns true.
<div ng-class="{showing: myDataFinallyLoaded}">...</div>
In this case, if myDataFinallyLoaded is true, the div has a showing class attached.
2. When the div has a showing class name attached, animate it into view.
div {
transform: translate(-100%) scale(0);
opacity: 0;
transition: transform 0.5s ease, opacity 0.5s ease;
}
div.showing {
/* Any CSS rules can go in here! */
transform: translate(0px) scale(1);
opacity: 1;
}
3. When your condition becomes true, update the scope.
someRandomAPI.loadEverything().then(function() {
$scope.myDataFinallyLoaded = true;
});
Here is the description of my goal:
I've got box with display: none
At some moment I need to display it with opacity animation.
Here my solution:
1. transition: opacity 0.5s ease-in-out;
2. On first step set display: block; opacity: 0
3. On second step set display: block; opacity: 1, do it in setTimeout() to apply first step.
The problem is that first step applied only in some cases - sometimes it works / sometimes doesn't and browser just skips first step. I thought changing setTimeout to requestAnimationFrame should fix the problem but it doesn't - check my example
Why setTimeout / requestAnimationFrame does not force browser to apply first step? How to force browser to apply first step before applying second one?
Solution: http://jsfiddle.net/sxny7zs2/
.box{display:none} should be .box{display: block;}
When you set display:none you remove the object from the DOM almost entirely. By resetting to display:block you bring the object back fully and it begins to interact with other objects. The display feature is not meant for animations but for removing objects from interfering with others.
I suspect this is the villain:
$box.removeClass('is-animate-enter').addClass('is-animate-active');
By removing is-animate-enter class you trigger display:none; before you are able to add your next class. This means the object is unloaded from the view. Meanwhile when you do is-animate-active you instantly set display:block and opacity:1. As far as the browser is concerned you are creating a new element, not modifying an old one here. As previously stated, when toggling the display you are actually loading and unloading an object so no animation is possible.
Maybe .switchClass() could fix this but I'm not sure, to reiterate the display command is for loading and unloading and not for animations.
In the past with JS transitions, one could specify some behavior to happen after the transition, via a callback. E.g.
//jQuery
function hideTheContent() {
$('#content').fadeOut(1000,function() {
mandatoryLogicAfterHidingContent();
});
}
Now with CSS3 transitions, we can do something like this.
//CSS
#content {
opacity:1;
transition: opacity 1s;
}
.hidden {
opacity:0;
transition: opacity 1s;
}
//jQuery
function hideTheContent() {
$('#content').addClass('hidden');
// still need to run mandatoryLogicAfterHidingContent
}
I know of the new transitionend events, so hypothetically we could do something like this:
//jQuery
function hideTheContent() {
$('#content').on('transitionend', function(e) {
mandatoryLogicAfterHidingContent()
})
$('#content').addClass('hidden');
}
but since we're now separating out the JS from the UI, it's possible that someone else could come along with a new UI where content-hiding is instant, remove the CSS3 transition, and inadvertently break the JS call to mandatoryLogicAfterHidingContent().
Another scenario, say I have a box and there's a reset function that resizes said box to size 50x50 (with transition), then calls a mandatoryLogicAfterResize(). If I call the reset fn twice, I don't think the transition event is guaranteed to fire in this case - but I still need mandatoryLogicAfterResize to run both times.
In more complex codebases, I also worry about other transitions getting called before the specific transition I'm targeting. and prematurely triggering the mandatoryLogic(). I guess the core problem here is, with CSS3 transitions, how can I defensively tie the mandatoryLogicAfterHidingContent() event handler with my and only my invocation of addClass('hidden'), like I could with the jQuery callback?
I am simply changing changing the color and background-color of a button when I click on it.
<input type="button" value="click me" id="myButton" onclick="ChangeColor()"/>
The CSS of this button contains CSS transition for the color and background-color, however, on the :hover pseudo-element I didn't add any styles, I didn't change the color.
#myButton{
color:#3399FF;
background-color:#FFFFFF;
/* These transitions are supposed to change the color in case I hover over the button */
-webkit-transition: background 0.5s,color 0.5s;
-moz-transition: background 0.5s,color 0.5s;
transition: background 0.5s,color 0.5s;
}
#myButton:hover{
/* But since there's nothing here, the color won't change when I hover */
}
Now, when I change the styles via JavaScript, they change while using the transitions, means, the colors will change after 0.5s, and not instantly.
function ChangeColor()
{
document.getElementById("myButton").style.color = "#FFFFFF";
document.getElementById("myButton").style.backgroundColor = "#3399FF";
}
This is a really good thing, and I like it, but I'm just wondering, how does JavaScript respect CSS3 transitions? Is there any documentation for this?
Your transitions are applied whenever the value of the property is changed. It doesn't matter whether you change it on hover, focus, resize (with a media query for example), click or any other event via JavaScript.
In general, you have a transition between two states of the element. You first define the initial state:
#myButton {
color: #39f;
background: #fff;
transition: .5s;
}
When you change the value of either of those two properties (and it doesn't matter whether you do this using the :hover pseudo-class or JavaScript), your element will go into another state and you are going to have a transition between the values of the properties from the initial state and those from this new state.
The method you're using to change the style with JavaScript is essentially a way of manually changing the style attribute directly on the element itself. Any time the style changes to something else and you have a transition defined for it, that transition will activate to change to the new style. That includes changes that JavaScript makes to the styles.
if i apply a style to an element and immdiatily afterwards add css transition styles, the transition is applied to the style preceeding. this might not always be the intention.
i found a solution by using settimeout (0), is there any cleaner/more correct approach known ?
http://jsfiddle.net/nicib83/XP9E7/
$("div").css("opacity", 1);
$("div").css("-webkit-transition", "all 0.35s");
/* Works
window.setTimeout(function () {
$("div").css("-webkit-transition", "all 0.35s");
}, 0);
*/
best regards
Edit:
i didn't mean how best to set css styling but how to sequentially set styles when the first style should be applied without the second being active at that time but only afterwards, i wan to add transition afterwards. settimeout fixes it, best solution ?
It's much better to pre-define a class that contains both of the properties you want to apply, and add that class programmatically to the element. Both of the properties will be applied together.
.myClass {
opacity: 1;
-webkit-transition: all 0.35s;
}
$("div").addClass("myClass");
You could take a page from the book of Twitter Bootstrap:
fade {
opacity: 0;
-webkit-transition: opacity 0.15s linear;
-moz-transition:opacity 0.15s linear;
-o-transition:opacity 0.15s linear;
transition:opacity 0.15s linear;
}
.fade.in{
opacity:1;
}
then programatically add the .in class when you want it to fade in:
$("div").addClass("in");
with your original div looking something like:
<div class="fade">Box</div>
I've been running up against this myself and also found the setTimeout solution. After some research the issue is how the browser handles scheduling. The JavaScript runs in its own thread separate from the threads dealing with the UI and the DOM (which is why issues like UI blocking happen).
In cases like this both JavaScript statements run before the document registers the first change and it ends up applying both classes at the same time. setTimeout(fn,0) effectively makes the function asynchronous and shunts the functions to run at the next available opportunity. This allows the UI thread to catch up before the next class is added.