Slide Transitions Between Bootstrap Tabs Using CSS3 & jQuery

Slide Transition Between Bootstrap Tabs

The goal here was to make bootstrap tabs show a smooth slide transition between tabs. We do this by adding three CSS classes during the transition phase. Overall, it works pretty well, although it could be smoother.

HTML

<ul class="nav nav-tabs nav-justified">
  <li class="active"><a href="#tab1" class="text-uppercase">Login</a></li>
  <li><a href="#tab2" class="text-uppercase">Register</a></li>
</ul>

<div class="tab-content">
  <div id="tab1" class="content-pane is-active">
    <div>Tab 1 Content</div>
  </div>
  <!-- /#tab1 -->

  <div id="tab2" class="content-pane">
    <div>Tab 2 Content</div>
  </div>
  <!-- /#tab2 -->
</div>
<!-- /.tab-content -->

This is the basic bootstrap html for tabs. Two important things to note are:

  • The href of the tabs has to match the id of the associated content pane
  • The content panes need to be wrapped in a  .tab-content class

CSS

.tab-content {
  position: relative;
  overflow: hidden;
}
.tab-content.is-animating {
  position: absolute;
  top: 0;
  left: 15px;
  right: 15px;
  width: auto;
}

.content-pane {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  margin: 0;
  width: 100%;
  opacity: 0;
  -webkit-transform: translateX(100%);
          transform: translateX(100%);
}
.content-pane.is-active {
  position: relative;
  opacity: 1;
  -webkit-transform: translateX(0%);
          transform: translateX(0%);
}
.content-pane.is-exiting {
  opacity: 0;
  -webkit-transform: translateX(-100%);
          transform: translateX(-100%);
}
.content-pane.is-animating {
  -webkit-transition: opacity 400ms ease-out, -webkit-transform 400ms ease-out;
  transition: opacity 400ms ease-out, -webkit-transform 400ms ease-out;
  transition: opacity 400ms ease-out, transform 400ms ease-out;
  transition: opacity 400ms ease-out, transform 400ms ease-out, -webkit-transform 400ms ease-out;
}

Our .tab-content class has overflow: hidden set because all of the content panes appear side by side. During the transition we slide the content panes to the left and give them a position: relative, which makes them appear first.

The .is-exiting class is used to move the content pane that is currently in view, out of view, by translating it 100% (the width of itself).

The .is-animating class is used animate the opacity and the transition attributes.

The .is-active class is used to show the active content pane.

Javascript

The javascript controls the which of the three above classes are added and at what time. This is what makes the slide effect initiate and finish.

var $tabs = $('ul.nav-tabs');

// show the appropriate tab content based on url hash
if (window.location.hash) {
  showFormPane(window.location.hash);
  makeTabActive($tabs.find('a[href="' + window.location.hash + '"]').parent());
}

// function to show/hide the appropriate content page
function showFormPane(tabContent, paneId) {
  var $paneToHide = $(tabContent).find('div.content-pane').filter('.is-active'),
    $paneToShow = $(paneId);

  $paneToHide.removeClass('is-active').addClass('is-animating is-exiting');
  $paneToShow.addClass('is-animating is-active');

  $paneToShow.on('transitionend webkitTransitionEnd', function() {
    $paneToHide.removeClass('is-animating is-exiting');
    $paneToShow.removeClass('is-animating').off('transitionend webkitTransitionEnd');
  });
}

// change active tab
function makeTabActive($tab) {
  $($tab).parent().addClass('active').siblings('li').removeClass('active');
}

// show/hide the tab content when clicking the tab button
$tabs.on('click', 'a', function(e) {
  e.preventDefault();

  makeTabActive($(this));
  showFormPane($(this).closest('.container'), this.hash);
});

As always the code is commented, as should yours be when you write it 🙂

The way the correct tab is selected and slide animation performed is through the url hashes. This is why it was important to have the tab href match the content pane id.

Update 12/2/2016

Added support for multiple tabs on one page

Update 4/24/2017

I refactored the JS to use object-oriented JS as well as included some additional optimizations. You can see how there is now only one function for toggling the tabs.

'use strict';

const Tabs = {
  init() {
    let promise = $.Deferred();
    this.$tabs = $('ul.nav-tabs');
    this.checkHash();
    this.bindEvents();
    this.onLoad();
    return promise;
  },

  checkHash() {
    if (window.location.hash) {
      this.toggleTab(window.location.hash);
    }
  },

  toggleTab(tab) {
    // targets
    var $paneToHide = $(tab).closest('.container').find('div.content-pane').filter('.is-active'),
      $paneToShow = $(tab),
      $tab = this.$tabs.find('a[href="' + tab + '"]');

    // toggle active tab
    $tab.closest('li').addClass('active').siblings('li').removeClass('active');

    // toggle active tab content
    $paneToHide.removeClass('is-active').addClass('is-animating is-exiting');
    $paneToShow.addClass('is-animating is-active');
  },

  showContent(tab) {

  },

  animationEnd(e) {
    $(e.target).removeClass('is-animating is-exiting');
  },

  tabClicked(e) {
    e.preventDefault();
    this.toggleTab(e.target.hash);
  },

  bindEvents() {
    // show/hide the tab content when clicking the tab button
    this.$tabs.on('click', 'a', this.tabClicked.bind(this));

    // handle animation end
    $('div.content-pane').on('transitionend webkitTransitionEnd', this.animationEnd.bind(this));
  },
  
  onLoad() {
    $(window).load(function() {
      $('div.content-pane').removeClass('is-animating is-exiting');
    });
  }
}

Tabs.init();
Mike Doubintchik

Author Mike Doubintchik

More posts by Mike Doubintchik

Join the discussion 5 Comments

Leave a Reply