How to implement a flip over effect using AngularJS animations? - angularjs

What would be the best way to achieve a flip over effect using AngularJS animations?
I would like the flip over effect to occur on click. Every time it's clicked, it should flip over to the other side.
Ideally, I guess, I'm looking for a directive implementation that uses Angular animations.

PLNKR - here is a seed of a configurable angular directive that provides 3d flipping functionality. I do not see any good reason why to use ngAnimate for it.
basic usage
<flip flip-width="200px" flip-height="100px">
<flip-panel>
content-front
</flip-panel>
<flip-panel>
content-back
</flip-panel>
</flip>
Comments
It appends css-styles on its own, to be fully independent.
In a proper, generic directive all names should be configurable.
flip-width and flip-height sets style of flip and both flip-panels.
Directive makes some basic check, if both front and back are set.
First flip-panel is front and the second is back.
Due to usage of transclusion content of the flip-panel may be arbitrary html. (you are right Misha no transclusion needed)
It only works in -webkit. (update to make it work in Firefox, just duplicate all pieces with -webkit with no prefix - you do not need -moz)
UPDATE
PLNKR - here is an updated and extended version. It shows what I meant by making the directive configurable. In more details:
Introduced flipConfig via provider, that allows to set in app.config:
default dimensions
css class names
speed of the transition
if the flip action is triggered by a click on the panel
Introduced flip-show attribute that specifies which side to show.
Changing flip-show we can trigger the flip action from outside of the directive.
It works in Firefox and [almost:-)] in IE11.
(btw: it is just a seed and it may be improved in a lot of ways. E.g: specifying axis, specifying origin of the transform, specifying radius and margin of the panels, allowing flip on hover, defaults colors, margins and so on)

I had the same usecase just recently for an angular memory game.
My implementation is the same by the idea of the other answers. I also released the flipping code along with a DEMO.
Github: https://github.com/zwacky/angular-flippy
P.s.: Looks i'm late to the party ;)

You can use ng-click and ng-class to add a class when the flip container is clicked.
<div class="flip-container" ng-click="flip = !flip" ng-class="{'flip': flip}">
<div class="flipper">
<div class="front" style="background: lightblue;">
front
</div>
<div class="back" style="background: lightgreen;">
back
</div>
</div>
</div>
This is essentially the angular way of doing what Walsh suggested in his article:
Adding the flip class to the container element will flip the card using JavaScript -- no user hover required. A JavaScript comment like document.querySelector("#myCard").classList.toggle("flip") will do the flip!
The only change to David Walsh's css was removing the :hover selector - the html structure is unchanged. It works nicely in chrome and firefox.. but the flip isn't as pretty in IE.
Here is a working demo: http://plnkr.co/edit/0dn775vpuoOeh2PS1T6k?p=preview
Update
I created a simple directive to encapsulate this basic technique. It allows you to flip over a black card, to reveal a picture on the other side.
app.directive("flipReveal", function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'template.html',
scope: {
url: '=',
flip: '='
}
}
})
Here is a link to a new demo: http://plnkr.co/X4pSav

Disclaimer Based on #artur's answer https://stackoverflow.com/a/23139242/1319998 , but hopefully both simplified and made more flexible.
A custom directive is the way to go, one that can be used as:
<flip flip-side="{{side}}">
<flip-front>
Front side contents
</flip-front>
<flip-back>
Rear contents
</flip-back>
</flip>
I think it should have certain properties:
Programatically controlled by an attribute. In this case, a string that is equal to 'front' or 'back'
<flip flip-side="{{side}}">....</flip>
this would allow programmatic access via the surrounding scope.
Integrated with ngAnimate/$animate. Specifically, if ngAnimate is removed or disabled, the animation should not occur, but the reveal of the other side happen immediately. Using $animate.addClass/$animate.removeClass would achieve this, adding/removing a flip-visible class together with display:block and display:none styles to make sure the right side is visible/hidden when the animations are disabled.
flip > flip-front, flip > flip-back {
display: none;
}
flip > .flip-visible {
display: block;
}
Controlled by CSS, with defaults. So if you want to change the duration of the flip, it's a CSS, and not a Javascript, addition.
So it will have a style sheet to add styles required for the various stages of $animate.addClass / $animate.removeClass CSS animations explained at Year of Moo and $animate docs . The class will be flip-visible, so the extra classes will be .flip-visible-add, .flip-visible-add-active, .flip-visible-remove, and .flip-visible-remove-active classes.
The full set of styles can be seen at http://plnkr.co/edit/bbYbMhiURnm6FqC9patp?p=preview, but the main construction is of the form:
.flip-visible-add {
// Initial setup: time and initial, pre-animation, transform
}
.flip-visible-add.flip-visible-add-active {
// Target transform
}
Putting all this together, the directive is quite short:
app.directive("flip", function($animate) {
return {
restrict : "E",
controller: function($scope, $element, $attrs) {
var elements = {
'front': $element.find('flip-front'),
'back': $element.find('flip-back')
};
$attrs.$observe('flipSide', function(visibleSide) {
visibleSide = visibleSide || 'front';
var otherSide = visibleSide == 'front' ? 'back' : 'front';
$animate.removeClass(elements[otherSide], 'flip-visible');
$animate.addClass(elements[visibleSide], 'flip-visible');
});
}
}
});
This can all be seen in an example, together with the stylesheets to make it all work, at http://plnkr.co/edit/bbYbMhiURnm6FqC9patp?p=preview

I realise there is a benefit to not integrating with the $animate service, and having a purely class-based solution.
If you use $animate with addClass and removeClass, but interrupt the animation (say, by clicking quickly and repeatedly on the element), the animation will 'jerk' to its end/starting point, and then animate from that position, at least on Chrome. Using a pure CSS solutions avoids this issue, and always animates from the exact current point, giving a smoother effect.
An added benefit is the solution is also simpler, and you don't need a custom directive.
For example, the HTML can be as follows:
<flip class="{{side === 'front' ? 'flip-front' : 'flip-back'}}">
<flip-front>
Front side contents
</flip-front>
<flip-back>
Rear contents
</flip-back>
</flip>
I use custom elements, but they don't need to have any directives attached: they are just for CSS to hook into:
flip > flip-front, flip > flip-back {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
/* Time can be overriden */
transition: -webkit-transform 0.5s;
transition: transform 0.5s;
}
/* Front visible */
flip > flip-front {
-webkit-transform: perspective(800px) rotateY(0);
transform: perspective(800px) rotateY(0);
}
flip > flip-back {
-webkit-transform: perspective(800px) rotateY(180deg);
transform: perspective(800px) rotateY(180deg);
}
/* Back visible */
flip.flip-back > flip-front {
-webkit-transform: perspective(800px) rotateY(-180deg);
transform: perspective(800px) rotateY(-180deg);
}
flip.flip-back > flip-back {
-webkit-transform: perspective(800px) rotateY(0);
transform: perspective(800px) rotateY(0);
}
This can be seen in a demo at http://plnkr.co/edit/A7IeGa1JEsZishmTDTaK?p=preview

I would simply add / remove a class on click.
If you want to hook into the angular animation system then take a look at the $animate service, in particular add/remove/setClass(). The service is usually used in directives. You might want to create a directive that reacts on a click event and triggers the animation. You even get informed when the animation has completed.
Chances are that it's not worth it ;)

You are going to want to create 3 divs.
<div class="wrapper">
<div class="front"></div>
<div class="back"></div>
</div>
You then position back behind front using z-index, and flip it upside down using rotateX (-180deg or so). Set a transition on wrapper as well.
Then, on click of wrapper, rotateX(+180deg). This will pretty much infinitely flip it over.
** Update: For angular, bind to click and use setClass to toggle between two classes on wrapper, one at rotateX(0deg) , the other at rotateX(180deg)

Here is a slightly modified version of artur's answer:
DEMO
angular.module('FlipDemo', []).directive("flip", function() {
return {
restrict : "A",
scope: true,
link: function(scope, element) {
var $panels = element.css({ position: 'relative' }).children().addClass("flip-panel");
var frontPanel = $panels.eq(0);
var backPanel = $panels.eq(1);
scope.showFrontPanel = function() {
frontPanel.removeClass("flip-hide-front-panel");
backPanel.addClass("flip-hide-back-panel");
};
scope.showBackPanel = function() {
backPanel.removeClass("flip-hide-back-panel");
frontPanel.addClass("flip-hide-front-panel");
};
scope.showFrontPanel();
}
}
});
.flip-panel {
position: absolute;
width: 100%;
height: 100%;
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
-webkit-transition: -webkit-transform .4s;
-moz-transition: -moz-transform .4s;
-webkit-transform: perspective(800px) rotateY(0deg);
-moz-transform: perspective(800px) rotateY(0deg);
}
.flip-hide-back-panel {
-webkit-transform: perspective(800px) rotateY(180deg);
-moz-transform: perspective(800px) rotateY(180deg);
}
.flip-hide-front-panel {
-webkit-transform: perspective(800px) rotateY(-180deg);
-moz-transform: perspective(800px) rotateY(-180deg);
}
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.0-beta.1/angular.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body ng-app="FlipDemo">
<div style="width: 100px; height: 150px">
<div flip style="width: 100%; height: 100%">
<div style="background-color: green">
<div>Front</div>
<button ng-click="showBackPanel()">Show Back</button>
</div>
<div style="background-color: blue">
<div>Back</div>
<button ng-click="showFrontPanel()">Show Front</button>
</div>
</div>
</div>
<br>
<div style="width: 150px; height: 100px">
<div flip style="width: 100%; height: 100%">
<div style="background-color: green">
<div>Front</div>
<button ng-click="showBackPanel()">Show Back</button>
</div>
<div style="background-color: blue">
<div>Back</div>
<button ng-click="showFrontPanel()">Show Front</button>
</div>
</div>
</div>
</body>
</html>
Main differences:
Works in Chrome and Firefox.
More flexibility with when the flip happens.
Just one directive rather than two. Less code.
I took the CSS outside of the directive for clarity sake.

Related

ng-animate and ng-if cause transition not to played in Internet Explorer

I have a simple div which has a transition. It transitions from a yellow background to a red one.
<div class="foo" ng-class="{'foo-visible': vm.visible}">
The transition is played once the foo--visible class is added to the div.
This works fine in Chrome, IE...etc.
However once I add a wrapper div around it, the transition stops working in Internet Explorer (tested with IE10).
<div class="foo--wrapper" ng-if="vm.visible">
<div class="foo" ng-class="{'foo--visible': vm.visible}">
</div>
I need to include ngAnimate in this case. Then it works in Chrome, but in IE I immediately get the red div, the transition is never played. No transitionstart (IE only) or transitionend events are fired.
Here's a plunker illustrating the issue:
http://plnkr.co/vpJzudnLxTwoJGZoZaNd
Does anybody have an idea what is causing this?
This plnkr adds two similar boxes to yours. http://plnkr.co/edit/lkyWHu?p=preview
<div ng-if="vm.visible">
<div class="animate-box animate-if">
<h2>Inner Class</h2>
</div>
</div>
<div class="animate-box animate-if" ng-if="vm.visible">
<div>
<h2>Outer Class</h2>
</div>
</div>
The "Outer" works in both IE and Chrome.
Chrome iterates the child animation for "Inner" in a way that makes sense. Internet Explorer, as for your example, does not.
On closing, neither do the child animation because the delay to remove the parent is zero.
An interesting point though, IE and Chrome appear to work the same for the authoritative answer at https://docs.angularjs.org/api/ngAnimate/directive/ngAnimateChildren
The CSS I used was:
.animate-box {
height: 100px;
width: 100px;
background-color: red;
}
.animate-if.ng-animate {
transition: all 3s linear;
}
.animate-if.ng-enter,
.animate-if.ng-leave.ng-leave-active {
background-color: yellow;
opacity: 0;
}
.animate-if.ng-leave,
.animate-if.ng-enter.ng-enter-active {
background-color: red;
opacity: 1;
}

Parent > Child animation unexpected - different Angular 1.3 to 1.4

I'm not sure if this is a bug, or I have a lack of understanding of the expected behaviour.
On the following codepen (With Angular 1.4) I expect the parent to animate followed by the child items to animate. However, the parent does not animate and the child items still wait for the transition period.
In this codepen (With Angular 1.3) the animations work as expected.
Is this a bug, am I missing something, or has something changed in 1.3 to 1.4 that I have not followed?
Code for completion
HTML
<div ng-app="app" ng-controller="TestCtrl as test">
<button ng-click="toggleItems()">Toggle Items</button>
<div class="parent" ng-show="items">
<p class="child" ng-repeat="item in items">{{item.name}}</p>
</div>
</div>
CSS
.parent {
background:#f00;
transition:all linear 0.5s;
}
.parent.ng-hide {
opacity:0;
}
.child {
background:#0f0;
transition:all linear 0.5s;
}
.child.ng-enter {
opacity:0;
}
JS
var app = angular.module('app', ['ngAnimate'])
app.controller('TestCtrl', function($scope){
$scope.items = null;
$scope.toggleItems = function(){
if( $scope.items ){
$scope.items = null;
} else {
$scope.items = [
{name: 'item 1'},
{name: 'item 2'},
{name: 'item 3'},
];
}
};
});
I found out what was the issue. But it finally seems like an Angular bug or just an outdated angular css:
1.3.5
Using ng-hide with ng-animate will apply :
ng-hide-animate ng-hide-add ng-hide-add-active when added.
ng-hide-animate ng-hide-remove ng-hide-remove-active when removed.
This works well since i found this line of CSS about ng-hide :
.ng-hide:not(.ng-hide-animate) {
display: none !important;
}
This mean your div will not be display when there is no animation. When there is an animation it will show and produce the animation.
1.4.0
In the official documentation Angular added this :
CSS Class-based Animations Class-based animations (animations that are
triggered via ngClass, ngShow, ngHide and some other directives) have
a slightly different naming convention. Class-based animations are
basic enough that a standard transition or keyframe can be referenced
on the class being added and removed.
For example if we wanted to do a CSS animation for ngHide then we
place an animation on the .ng-hide CSS class:
<div ng-show="bool" class="fade"> Show and hide me </div>
<button ng-click="bool=true">Toggle</button>
<style> .fade.ng-hide { transition:0.5s linear all; opacity:0; }
</style>
This mean that on 1.4.0 using ng-hide will simple add and remove ng-hide class.
**/!** In your codePen exemple i found that this line is still there in the CSS
.ng-hide:not(.ng-hide-animate) {
display: none !important;
}
Wich that each time you add ng-hide or remove it, you'll simple set "display:none" wich mean that no animation can be run. You'll need to override this behavior to use proper animations.
Doing this :
.parent.ng-hide {
display:block!important;
opacity:0;
}
Could do the trick but i'm not really sure that this is a safe way to do this.
At least i found the explanation, it would require more investigation to know how to deal with it in a proper way.
Hope it helped.

How to make simple animation using ngAnimate

I am not able to understand how the ngAnimate works exactly. here is my doubt.
1) ngAnimate - only works on directives?
2) how to make ng-animate work without the directive
3) Any of above way, how to add call back after animation complete?
Because i see all the animation examples only with directives.
I have a small demo here, any one help me to animation both without directive and with directive approach to simply adding a class name as `fade'?
my CODE:
<div class="container" ng-app="myApp">
<div class="content" ng-controller="count">
<h1 ng-click="animate()">Click ME</h1>
<h2>Let me Fade</h2>
</div>
</div>
<div class="container" ng-app="myApp">
<div class="content" ng-controller="count">
<h1 ng-click="animate()">Click ME</h1>
<h2>Let me Fade</h2>
</div>
</div>
Demo to update
I am not able to understand how the ngAnimate works exactly. here is
my doubt.
ngAnimate is a module that provides support for animations in angular apps. There are two ways to make use of animations when ngAnimate is used: by using CSS and JavaScript. For CSS based animations, angularjs adds a class ng-enter/ng-leave whenever an element is shown/removed from 'view'. You simply need to play with these classes to make the animation work!
Prerequisite:
You would need to add the library for angular-animate
<script src="ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular-animate.js">
</script>
and include ngAnimate as the dependency in your myApp module.
var myApp = angular.module('myApp', ['ngAnimate']);
1) ngAnimate - only works on directives?
Yes. You cannot use ngAnimate without directive.
According to documentation, following directives are "animation aware":
ngRepeat, ngView, ngInclude, ngSwitch, ngIf, ngClass,
ngShow, ngHide, ngModel, ngMessages and ngMessage
2) how to make ng-animate work without the directive
You cannot!. Remember, even ng-click is a directive
3) Any of above way, how to add call back after animation complete?
Yes, You can add a callback after the animation is complete using the $animate service(which would usually be done in a custom directive) and use $animate.leave(element, [options]);
Have a look at this example for triggering events after the animation ends.
Finally, here is the updated demo you mentioned in question.
You may toggle a flag to true/false with each click on <h1> and make content inside <h2> hide/show based on flag.
<div class="container" ng-app="myApp">
<div class="content" ng-controller="count">
<h1 ng-click="animate()">Click ME</h1>
<h2 ng-if="flag" class="fade">Let me Fade</h2>
</div>
</div>
Also, you'd need to handle fade-effect with css
.fade.ng-enter {
transition:0.5s linear all;
opacity:0;
}
.fade.ng-enter.ng-enter-active {
opacity:1;
}
.fade.ng-leave {
transition:0.5s linear all;
opacity:1;
}
.fade.ng-leave.ng-leave-active {
opacity:0;
}
Hope it helps!
<div class="container" ng-app="myApp">
<div class="content" ng-controller="count">
<h1 ng-click="animate()">Click ME</h1>
<h2 ng-if="clicked" class="animate-if">Let me Fade</h2>
</div>
I added a variable named clicked which is set to true or false to animate the Let me Fade Text
var myApp = angular.module('myApp', []);
myApp.controller('count', function($scope) {
$scope.clicked=false;
$scope.animate = function () {
$scope.clicked=!$scope.clicked;
}
});
In this JS file upon clicking the click me button the variable clicked is set to true or false .
**
h2.fade {
opacity : 0;
transition: opacity 1s ease-in-out;
}
.animate-enter, .animate-leave {
transition: 500ms ease-in all;
position: relative;
display: block;
}
.animate-enter.animate-enter-active, .animate-leave {
left: 0;
}
.animate-leave.animate-leave-active, .animate-enter {
left: 500px;
}
**
Here in the css file i added css for the class animate which acts upon clicked variable if the variable is true it goes for animate-enter-active
otherwise it goes for leave-active

Wrapping Formstone Wallpaper jquery plugin in an AngularJS directive

Trying hard to implement the Formstone Wallpaper JQuery plugin into my AngularJS application. I followed the instructions on the site just to learn that AngularJS and JQuery don't work well together. In many articles in the net it was mentioned that I had to wrap jquery plugins in AngularJS directives to use it the right way. I found this tutorial by David Boike on how to warp the Knob jquery plugin into an AngularJS directive.
I tried to follow along and achieve that for the Formstone Wallpaper plugin. This is my attempt:
'use strict';
angular.module('app').directive('formstoneWallpaper',[function() {
return {
restrict: 'E',
link: function (scope, elem, attrs) {
elem.wallpaper({
source: {
poster: attrs.poster,
mp4: attrs.mp4,
ogg: attrs.ogg,
webm: attrs.webm
}
});
}
};
}]);
Then my html markup changes to:
<div class="container-fluid">
<div class="row wall">
<formstone-wallpaper
poster="modules/launch/videos/ocean.jpg"
mp4="modules/launch/videos/ocean.mp4"
ogg="modules/launch/videos/ocean.ogv"
webm="modules/launch/videos/ocean.webm"
></formstone-wallpaper>
</div><!--row-->
</div><!--container-->
Yet this does not result in the desired full-width responsive video wallpaper. Instead the width and height is somehow set to 0px. I therefore do not see anything on my website. However, when I kill the wallpaper-container and wallpaper-media classes, the problem is partially resolved in that the video finally appears (however, it is not responsive and fittet to screen size-the reason why we are doing this exercise in the first place). Here is a screenshot from my chrome inspector.
Can someone help me to improve the code for the directive?
friends, it turned out the above directive is actually alright. The real problem that I had was my lack of understanding of nested relative positioning. I used this "resize" directive to dynamically set the height of my container to the height of the viewport.
This is the css that finally fixes the my issue:
formstone-wallpaper {
display: block;
position: relative;
width: auto;
height: auto;
padding: 100px 0;
min-height: 100%;
background: no-repeat center center;
background-attachment: scroll;
background-size: cover;
}
Set the "resize" attribute on your container like so:
<div class="container-fluid" data-ng-style="style()" resize>
<div class="row">
<formstone-wallpaper
poster="modules/launch/videos/ocean.jpg"
mp4="modules/launch/videos/ocean.mp4"
ogg="modules/launch/videos/ocean.ogv"
webm="modules/launch/videos/ocean.webm"
></formstone-wallpaper>
</div><!--row-->
</div><!--container-->

Tell ngAnimate to only animate ngShow/ngHide

I have an AngularJS 1.2.2 web application with a <div> that I show/hide based on some $scope property. Using the ngAnimate module, I animate the showing and hiding of the <div>.
<div id="square" ng-show="showSquare" class="animate-shiny"></div>
I also have a class I want to place on this <div> and for this I use ngClass.
<div id="square" ng-show="showSquare" class="animate-shiny" ng-class="{ cool: extraCool }"></div>
And as it so happens, sometimes that class gets applied at the same moment as when the <div> is shown/hidden. This causes the show/hide animation to not work anymore, apparantly it finds ngClass more interesting to animate, even though I don't want to use ngAnimate for that animation.
Here's a Plnkr that demonstrates the behavior. Clicking the show/hide button works great, clicking the make cool button works great, but the button that combines these two causes the show/hide animation to break.
How do I fix this? And can I do it without manually addressing $animate?
Thanks in advance!
The problem is that you are trying to animate using the class and not discriminate between when things should animate. That is, your transition effect applies to the class in general, which ng-animate perceives as having to do work whenever that class is referenced. I modified your css a bit to get pretty close, if not exactly, what you want:
#square {
width: 50px;
height: 50px;
background-color: blue;
transition: 0.4s all ease-out;
}
#square.cool {
box-shadow: 0 0 10px 3px green;
background-color: lightgreen;
}
#square.ng-hide-add, #square.ng-hide-remove
{
display: block !important;
}
#square.ng-hide-remove, #square.ng-hide-add.ng-hide-add-active{
margin-left: -80px;
opacity: 0;
}
#square.ng-hide-remove.ng-hide-remove-active, #square.ng-hide-add{
margin-left: 0;
opacity: 1;
}
Here is the new plunkr so you can play with it: http://plnkr.co/edit/a7wiZfCrEGCXfIDSvF9r?p=preview
If you want to ONLY animate the show/hide and do not want a transition for the color, simply move the transition to the #square.ng-hide-add, #square.ng-hide-remove declaration.

Resources