Created
August 8, 2015 19:49
Conditional Animations And Transition Timing In AngularJS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> | |
— | |
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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