Fade effect for tabs in ui-bootstrap (Angular.JS)
Since tabset use ng-class
to control "active" tab, which allow us to define fade effect with angular animation by setting opacity=0 when "active" class is removed/attached.
First, you need to load ngAnimate
module by including angular-animate.js
and set up dependency.
Add to your <head>
:
<script src="https://code.angularjs.org/1.2.24/angular-animate.js"></script>
Set module dependency:
angular.module("myApp", ["ui.bootstrap", "ngAnimate"]);
Now add animation class to your tabset.
<tabset class="tab-animation"> <tab heading="Tab1">Some content</tab> <tab heading="Tab2">Other content</tab></tabset>
Put following code into your css file:
/* set reference point */.tab-animation > .tab-content { position: relative;}/* set animate effect */.tab-animation > .tab-content > .tab-pane{ transition: 0.2s linear opacity;}/* overwrite display: none and remove from document flow */.tab-animation > .tab-content > .tab-pane.active-remove { position: absolute; top: 0; width: 100%; display: block;}/* opacity=0 when removing "active" class */.tab-animation > .tab-content > .tab-pane.active-remove-active { opacity: 0;}/* opacity=0 when adding "active" class */.tab-animation > .tab-content > .tab-pane.active-add { opacity: 0;}
That's all. You can check the demo on Plunker.
Also take a look at ngAnimate doc.
I ended up patching the ui-bootstrap file.I'm still a noob with AngularJS, so please forgive the lingo.This is an unconventional hack, and needs to be refactored with ng-animate, but it works.
Open ui-bootstrap-tpls-0.10.0.js and look for the 'tab' directive :
.directive('tab', ['$parse', function($parse) { return { require: '^tabset', restrict: 'EA', replace: true, templateUrl: 'template/tabs/tab.html', transclude: true, scope: { id:'@', // PATCH : GETTING TAB 'id' ATTRIBUTE heading: '@', onSelect: '&select', //This callback is called in contentHeadingTransclude //once it inserts the tab's content into the dom onDeselect: '&deselect' }, // ...
Notice the extra code for retrieving the id attribute value (via transclusion, I guess).
A few lines below, look for :
scope.$watch('active', function(active) {
and patch it like so :
scope.$watch('active', function(active) { // Note this watcher also initializes and assigns scope.active to the // attrs.active expression. setActive(scope.$parent, active); if (active) { tabsetCtrl.select(scope); scope.onSelect(); tab_id = attrs.id; $(".tab_pane_"+tab_id).hide(); // HIDE AT FIRST, SO IT CAN ACTUALLY FADE IN $(".tab_pane_"+tab_id).fadeIn(1000); // JQUERY TARGETING BY CLASS } else { scope.onDeselect(); tab_id = attrs.id; $(".tab_pane_"+tab_id).hide(); // JQUERY TARGETING BY CLASS } });
A few lines below, look for :
scope.select = function() {
and add inside :
$(".tab-pane").hide();
so all tab panes hide properly at first.
Then, look for :
angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { ...
and add the css class to the tab-pane element in the corresponding template, like so :
angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {$templateCache.put("template/tabs/tabset.html","\n" +"<div class=\"tabbable\">\n" +" <ul class=\"nav {{type && 'nav-' + type}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +" <div class=\"tab-content\">\n" +" <div class=\"tab-pane tab_pane_{{tab.id}}\" \n" + // CLASS NAME IS DYNAMIC" ng-repeat=\"tab in tabs\" \n" +" ng-class=\"{active: tab.active}\"\n" + " tab-content-transclude=\"tab\">\n" +" </div>\n" +" </div>\n" +"</div>\n" +"");}]);
Once the ui-bootstrap .js file is modified, you must edit your view template (where you fetch the tabs) and declare the 'id' attribute :
<!-- TABS --> <tabset justified="true"> <tab ng-repeat="tab in tabs" heading="{{tab.title}}" id="{{tab.id}}" > // ... TAB CONTENT
You should get the basic concept, currently it's not very elegant (to put it mildly).But it works.
In case you wonder how my tabs got id's, well, I injected them in my controller :
Tab1 = { id:1, 'ShortDescription': ShortDescription, 'FullDescription': FullDescription, 'TabContent': TabContent1, title: "ProductTabTitleDefault1", // active:true }; Tab2 = { id:2, 'ShortDescription': ShortDescription, 'FullDescription': FullDescription, 'TabContent': TabContent1, title: "ProductTabTitleDefault2", // active:true }; $rootScope.tabs = { 'Tab1': Tab1, 'Tab2': Tab2, };
Of course this is mockup data, but assuming your tabs and their content are dynamic, you can use a counter, and maybe use another key instead of "id" (but you'll have to change the rest accordingly).
I have an alternative solution to @user3413125's well-written solution above. It uses @keyframes to achieve a cross-fade, rather than a fade out followed by a fade in. See demo on Plunker
Here is the fade-in portion of the CSS (fade-out is similar):
.tab-animation > .tab-content > .tab-pane.active-add { animation: 1s fade-in;}@keyframes fade-in { from { opacity: 0; } to { opacity: 1; }}
The keyframe technique is taken from the AngularJs tutorial 14 - look for "CSS Keyframe Animations: Animating ngView", about halfway down the page.