Skip to content

Instantly share code, notes, and snippets.

@bennadel
Created August 8, 2015 19:49
Conditional Animations And Transition Timing In AngularJS
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
<title>
Conditional Animations And Transition Timing In AngularJS
</title>
<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body ng-controller="AppController">
<h1>
Conditional Animations And Transition Timing In AngularJS
</h1>
<p>
<a ng-click="toggleFriends()">Toggle Friends</a>
&mdash;
<a ng-click="toggleEnemies()">Toggle Enemies</a>
</p>
<!--
The list of friends will only animate the first 2 times it is rendered. After
that, it will show up and hide instantly.
-->
<div
ng-if="isShowingFriends"
class="box friends"
bn-conditional-animation="friend-list">
<h2>
Friends
</h2>
<ul>
<li ng-repeat="friend in friends">
{{ friend }}
</li>
</ul>
</div>
<!--
The list of enemies will only animate the first 5 times it is rendered. After
that, it will show up and hide instantly.
-->
<div
ng-if="isShowingEnemies"
class="box enemies"
bn-conditional-animation="enemies-list">
<h2>
Enemies
</h2>
<ul>
<li ng-repeat="enemy in enemies">
{{ enemy }}
</li>
</ul>
</div>
<!-- Load scripts. -->
<script type="text/javascript" src="../../vendor/angularjs/angular-1.4.3.js"></script>
<script type="text/javascript" src="../../vendor/angularjs/angular-animate-1.4.3.js"></script>
<script type="text/javascript">
// Create an application module for our demo.
angular.module( "Demo", [ "ngAnimate" ] );
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I control the root of the demo.
angular.module( "Demo" ).controller(
"AppController",
function AppController( $scope ) {
// I determine which boxes are being rendered.
$scope.isShowingFriends = false;
$scope.isShowingEnemies = false;
// I define the list of data points in each box.
$scope.friends = [ "Sarah", "Joanna", "Kim", "Tricia" ];
$scope.enemies = [ "Pam", "Anna", "Jane", "Sue", "Cat" ];
// ---
// PUBLIC METHODS.
// ---
// I toggle the rendering of the enemies list.
$scope.toggleEnemies = function() {
$scope.isShowingEnemies = ! $scope.isShowingEnemies;
};
// I toggle the rendering of the friends list.
$scope.toggleFriends = function() {
$scope.isShowingFriends = ! $scope.isShowingFriends;
};
}
);
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I add conditional-animation classes to the linked element on each rendering.
angular.module( "Demo" ).directive(
"bnConditionalAnimation",
function bnConditionalAnimation( conditionalAnimationService ) {
// Get the settings for conditional animation rendering.
var classPrefix = conditionalAnimationService.getClassPrefix();
var maxCount = conditionalAnimationService.getMaxCount();
// Return the directive configuration object.
return({
link: link,
restrict: "A"
});
// I bind the JavaScript events to the local view-model.
function link( scope, element, attributes ) {
// Each linked instances of this directive keeps count,
// individually, of the number of renderings. As such, the
// developer needs to either provide a unique name; or, just
// group this rendering with other non-provided names.
var animationKey = ( attributes[ "bnConditionalAnimation" ] || "*" );
// The whole point of this is to phase animations out over time.
// As such, there is a natural limit to the usefulness of this over
// time. Therefore, we're going to max out the actual class name
// after 10-renderings. If you need more granularity than that, you
// might be missing the intent here.
var count = Math.min(
conditionalAnimationService.incrementCount( animationKey ),
maxCount
);
// Add the appropriate nth rendering class.
attributes.$addClass( classPrefix + count );
}
}
);
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I work with the bnConditionalAnimation directive to help keep track of which
// elements have been linked, and how many times they have been rendered.
angular.module( "Demo" ).provider(
"conditionalAnimationService",
function conditionalAnimationServiceProvider() {
// I am the prefix used when adding the conditional class names to the
// rendered elements. This value will be followed by the numeric
// rendering count for the given instance.
// --
// Example: ( "conditional-animation-n" + 3 ).
var classPrefix = "conditional-animation-n";
// I am the maximum count that will actually be appended to the class
// prefix. The internal count will continue to increment; but, overall,
// when it comes to rendering, the class name will never go above this.
var maxCount = 10;
// Return the public API.
return({
setClassPrefix: setClassPrefix,
setMaxCount: setMaxCount,
$get: conditionalAnimationService
});
// ---
// PUBLIC METHODS.
// ---
// I set the class prefix to use when adding conditional class names.
function setClassPrefix( newClassPrefix ) {
classPrefix = newClassPrefix;
}
// I set the max value to be used when rendering class names.
function setMaxCount( newMaxCount ) {
maxCount = newMaxCount;
}
// ---
// SERVICE DEFINITION.
// ---
// I provide the conditional animation service.
function conditionalAnimationService() {
// I hold the count of each rendered item.
var cache = Object.create( null );
// Return the public API.
return({
getClassPrefix: getClassPrefix,
getCount: getCount,
getMaxCount: getMaxCount,
incrementCount: incrementCount
});
// ---
// PUBLIC METHODS.
// ---
// I return the class prefix for conditional classes.
function getClassPrefix() {
return( classPrefix );
}
// I return the number of times the given instance has been rendered.
function getCount( key ) {
return( getCachedInstance( key ).count );
}
// I return the max count that should be used when generating
// conditional class names. This value is capped while the underlying
// render count is unbounded.
function getMaxCount() {
return( maxCount );
}
// I increment the rendering count for the given instance.
function incrementCount( key ) {
return( ++ getCachedInstance( key ).count );
}
// ---
// PRIVATE METHODS.
// ---
// I return the cache of the given instance. If the cache does not
// yet exist, it is created and returned.
function getCachedInstance( key ) {
var normalizedKey = normalizeKey( key );
// Ensure the existence of the cache.
if ( ! cache[ normalizedKey ] ) {
cache[ normalizedKey ] = {
key: key,
count: 0
};
}
return( cache[ normalizedKey ] );
}
// I return a normalized key that won't collide with any other
// values on the Object prototype.
// --
// NOTE: Since we are using Object.create( null ) to setup the cache,
// this probably isn't necessary.
function normalizeKey( key ) {
return( "animation:" + key );
}
}
}
);
</script>
</body>
</html>
a[ ng-click ] {
color: red ;
cursor: pointer ;
text-decoration: underline ;
user-select: none ;
-moz-user-select: none ;
-webkit-user-select: none ;
}
div.box {
background-color: #F0F0F0 ;
border: 1px solid #CCCCCC ;
border-radius: 3px 3px 3px 3px ;
padding: 6px 20px 6px 20px ;
position: relative ;
}
/* Set up the friends timing. */
div.friends.ng-enter {
opacity: 0.0 ;
transition: 0s opacity ease ;
}
div.friends.ng-enter-active {
opacity: 1.0 ;
}
div.friends.ng-leave {
transition: 0s opacity ease ;
}
div.friends.ng-leave-active {
opacity: 0.0 ;
}
div.friends.conditional-animation-n1 {
transition-duration: 1s ;
}
div.friends.conditional-animation-n2 {
transition-duration: 0.5s ;
}
/* Set up the enemies timing. */
div.enemies.ng-enter {
opacity: 0.0 ;
transition: 0s opacity ease ;
}
div.enemies.ng-enter-active {
opacity: 1.0 ;
}
div.enemies.ng-leave {
transition: 0s opacity ease ;
}
div.enemies.ng-leave-active {
opacity: 0.0 ;
}
div.enemies.conditional-animation-n1 {
transition-duration: 1s ;
}
div.enemies.conditional-animation-n2 {
transition-duration: 0.85s ;
}
div.enemies.conditional-animation-n3 {
transition-duration: 0.6s ;
}
div.enemies.conditional-animation-n4 {
transition-duration: 0.4s ;
}
div.enemies.conditional-animation-n5 {
transition-duration: 0.15s ;
}
/* Set up the "info note" rendering. */
div.box.conditional-animation-n1:before,
div.box.conditional-animation-n2:before,
div.box.conditional-animation-n3:before,
div.box.conditional-animation-n4:before,
div.box.conditional-animation-n5:before,
div.box.conditional-animation-n6:before,
div.box.conditional-animation-n7:before,
div.box.conditional-animation-n8:before,
div.box.conditional-animation-n9:before,
div.box.conditional-animation-n10:before {
background-color: red ;
border-radius: 3px 0px 3px 0px ;
color: #FFFFFF ;
content: "Render n1" ;
font-size: 13px ;
left: 0px ;
position: absolute ;
padding: 5px 10px 5px 10px ;
top: 0px ;
}
div.box.conditional-animation-n2:before {
content: "Render n2" ;
}
div.box.conditional-animation-n3:before {
content: "Render n3" ;
}
div.box.conditional-animation-n4:before {
content: "Render n4" ;
}
div.box.conditional-animation-n5:before {
content: "Render n5" ;
}
div.box.conditional-animation-n6:before {
content: "Render n6" ;
}
div.box.conditional-animation-n7:before {
content: "Render n7" ;
}
div.box.conditional-animation-n8:before {
content: "Render n8" ;
}
div.box.conditional-animation-n9:before {
content: "Render n9" ;
}
div.box.conditional-animation-n10:before {
content: "Render n10" ;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment