<div ng-controller="mainCtrl" class="demo">
  <tab-layout scrollable selected="current">
    <tab><i class="material-icons">home</i></tab>
    <tab><i class="material-icons">favorite</i></tab>
    <tab><i class="material-icons">backup</i></tab>
    <tab><i class="material-icons">camera</i>&nbsp;camera</tab>
  </tab-layout>
  
  <pages selected="current">
    <page>A</page>
    <page>B</page>
    <page>C</page>
    <page>D</page>
  </pages>
</div>
*{font-family: 'Roboto', 'Noto', sans-serif;}

body{
  display: flex;
  display: -webkit-flex;
  display: -ms-flex;
  
  width: 100%;
  height: 100%;
  -webkit-justify-content: center;
    -ms-justify-content: center;
      justify-content: center;
  
  -webkit-align-items: center;
    -ms-align-items: center;
      align-items: center;
}

/* pages css */
pages  {
  color: white;
  text-align: center;
  display: block;
  -webkit-overflow-scrolling: touch;
  overflow: hidden;
  white-space: nowrap;
}

pages .container {
  white-space: nowrap;
  font-size: 0px;
}

pages page{
  display: inline-block;
  -webkit-transition: 800ms ease-out opacity;
    -moz-transition: 800ms ease-out opacity;
      -ms-transition: 800ms ease-out opacity;
        transition: 800ms ease-out opacity;
  
  font-size: 50px;
}

pages page.active {
  opacity: 1 !important;
}

/* tabs css */
tab-layout {
  background-color: #00bcd4;
  
  display: flex;
  display: -webkit-flex;
  display: -ms-flex;
  
  -webkit-align-items: center;
    -ms-flex-align: center;
      
  -webkit-align-items: center;
    -ms-align-items: center;
      align-items: center;
      
  height: 48px;
  font-size: 14px;
  font-weight: 500;
  overflow: hidden;
  
  -webkit-user-select: none;
    -ms-user-select: none;
      user-select: none;
        
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  -webkit-tap-highlight-color: transparent;


  -webkit-flex-direction: row-reverse;
    -ms-flex-direction: row-reverse;
      flex-direction: row-reverse;
}

tab-layout .tabs_container{
  position: relative;
  height: 100%;
  white-space: nowrap;
  overflow: hidden;
  
  -webkit-flex: 1 1 auto;
    -ms-flex: 1 1 auto;
      flex: 1 1 auto;
  
  touch-action: pan-y;
}

tab-layout .tabs_content{
  position: absolute;
  white-space: nowrap;
  height: 100%;
  -moz-flex-basis: auto;
    -ms-flex-basis: auto;
      flex-basis: auto;
}

tab-layout[scrollable] .tabs_content{
  position: absolute;
  white-space: nowrap;
}

tab-layout tab .tab-content {
  height: 100%;
  
  -webkit-transform: translateZ(0);
    transform: translateZ(0);
    
  -webkit-transition: opacity 0.1s cubic-bezier(0.4, 0.0, 1, 1);
    -moz-transition: opacity 0.1s cubic-bezier(0.4, 0.0, 1, 1);
      -ms-transition: opacity 0.1s cubic-bezier(0.4, 0.0, 1, 1);
        transition: opacity 0.1s cubic-bezier(0.4, 0.0, 1, 1);
  
  display: -ms-flexbox;
  display: -webkit-flex;
  display: flex;
  
  -webkit-flex-direction: row;
    -ms-flex-direction: row;
      flex-direction: row;
  -webkit-align-items: center;
    -ms-flex-align: center;
      align-items: center;
  -webkit-justify-content: center;
    -ms-flex-pack: center;
      justify-content: center;
  -webkit-flex: 1 1 auto;
    -ms-flex: 1 1 auto;
      flex: 1 1 auto;
}

tab-layout tab{
  display: -ms-inline-flexbox;
  display: -webkit-inline-flex;
  display: inline-flex;
  
  -webkit-align-items: center;
    -ms-flex-align: center;
      align-items: center;
  -webkit-justify-content: center;
    -ms-flex-pack: center;
      justify-content: center;
  -webkit-flex: 1 1 auto;
    -ms-flex: 1 1 auto;
      flex: 1 1 auto;
  position: relative;
  padding: 0 12px;
  overflow: hidden;
  cursor: pointer;
  -webkit-font-smoothing: antialiased;
  
  color: rgba(255, 255, 255, 0.8);
  height: 100%;
}

tab-layout .tabs_content{
  padding-left: 16px;
}

tab-layout tab[aria-selected="true"]{
  color: rgba(255, 255, 255, 1);
}

tab-layout .indicator{
  height: 2px;
  background-color: #ffff8d;
  position: absolute;
  bottom: 0;
}

tab-layout .indicator.right{
  transition: right 200ms;
}

tab-layout .indicator.left{
  transition: left 200ms;
}


/* demo */
.demo{
  width:420px;
}

.demo pages page{
  width: 100%;
  height: 100%;
  padding: 80px 0;
}

.demo pages page:nth-child(1) {
  background-color: #4285f4;
}

.demo pages page:nth-child(2) {
  background-color: #db4437;
}

.demo pages page:nth-child(3) {
  background-color: #0f9d58;
}

.demo pages page:nth-child(4) {
  background-color: #76daff;
}
var appname = 'layoutjs';
    
var app = angular.module(appname, []);

// Bootstrap angularjs app
angular.element(document).ready(function () {
    console.log("App: bootstraping.");
    angular.bootstrap(document, [appname]);
});

app.controller('mainCtrl',['$scope', function($scope){
  // main controller
  $scope.current = 0;
}]);

// pages directive
'use strict';

app.directive('pages', function(){
  return{
    restrict: 'E',
    template: '<div class="container" ng-transclude></div>',
    transclude: true,
    scope:{
      selected: '='
    },
    controller: function($scope, $element){
      var SLIDE_WIDTH = $element.width();
      var currentSlide = this.selected;
      var maxSlides = 0;
      var speed = 500;
      
      var slide = $element.children('.container');
        
      slide.swipe({
        triggerOnTouchEnd: true,
        swipeStatus: swipeStatus,
        allowPageScroll: "vertical",
        threshold: 75
      });
      
      this.selected = $scope.selected;
      
      var pages = [];
      this.addPage = function($element){
        pages.push($element);
      }
      
      this.count = function(){
        maxSlides = pages.length;
        return pages.length;
      }
      
      this.setActive = function($e){
        $element.find('page[aria-selected]').each(function(i, e){
          var $e = angular.element(e);
          if($e.attr('aria-selected') !== undefined){
            $e.removeAttr('aria-selected');
          }
        })
        
        $e.attr('aria-selected', 'true');
        //$e.focus();
        
        /*$element.scrollTo($e[0], 100, {
          onAfter : function () {
            requestAnimationFrame(function () {
              $e.addClass("active");
            });
          }
        });*/

      };
      
      var self = this;
      $scope.$watch('selected', function(n, o){
        if(n === undefined || n > self.count() || n < 0) return;
        currentSlide = n;
        $element.find('page').each(function(i, e){
          var $e = angular.element(e);
          if($e.data('id') === n){
            scrollSlides(SLIDE_WIDTH * n, speed);
            self.setActive(pages[currentSlide]);
            return;
          }
        })
      });
      
      /**
       * Catch each phase of the swipe.
       * move : we drag the div
       * cancel : we animate back to where we were
       * end : we animate to the next image
       */
      function swipeStatus(event, phase, direction, distance) {
      	//If we are moving before swipe, and we are going L or R in X mode, or U or D in Y mode then drag.
      	if (phase == "move" && (direction == "left" || direction == "right")) {
      		var duration = 0;

      		if (direction == "left") {
      			scrollSlides((SLIDE_WIDTH * currentSlide) + distance, duration);
      		} else if (direction == "right") {
      			scrollSlides((SLIDE_WIDTH * currentSlide) - distance, duration);
      		}

      	} else if (phase == "cancel") {
      		scrollSlides(SLIDE_WIDTH * currentSlide, speed);
      	} else if (phase == "end") {
      		if (direction == "right") {
      			previousSlide();
      		} else if (direction == "left") {
      			nextSlide();
      		}
      	}
      }

      function previousSlide() {
      	currentSlide = Math.max(currentSlide - 1, 0);
      	//scrollSlides(SLIDE_WIDTH * currentSlide, speed);
        $scope.selected = currentSlide;
        $scope.$apply();
      }

      function nextSlide() {
      	currentSlide = Math.min(currentSlide + 1, maxSlides - 1);
      	//scrollSlides(SLIDE_WIDTH * currentSlide, speed);
        $scope.selected = currentSlide;
        $scope.$apply();
      }

      /**
       * Manually update the position of the imgs on drag
       */
      function scrollSlides(distance, duration) {
      	slide.css("transition-duration", (duration / 1000).toFixed(1) + "s");

      	//inverse the number we set in the css
      	var value = (distance < 0 ? "" : "-") + Math.abs(distance).toString();
        
        slide.css({
         transform: "translate(" + value + "px,0)",
         MozTransform: "translate(" + value + "px,0)",
         WebkitTransform: "translate(" + value + "px,0)",
         msTransform: "translate(" + value + "px,0)"
        });
      }
      
    }
  }
})

app.directive('page', function(){
  return{
    restrict: 'E',
    require: '^pages',
    link: function($scope, $element, $attrs, pages){
      var id = pages.count();
      
      $element.data('id', id);
      
      pages.addPage($element);
    }
  }
});

// tabLayout directive
app.directive('tabLayout', function($timeout){
  return {
    restrict: 'E',
    template: '\
    <div class="tabs_container">\
      <div class="indicator"></div>\
      <div class="tabs_content" ng-transclude></div>\
    </div>',
    transclude: true,
    scope: {
      selected: '='
    },
    controller: function($scope, $element){
      $element
        .attr('role', 'tablist')
        .attr('tabindex', 0);
        
      var self = this;
      this.currentTab = $scope.selected;
      var tabs = [];
      
      this.addTab = function($element){
        tabs.push($element);
      }
      
      this.count = function(){
        return tabs.length;
      }
      
      var indicator = $element.find('.indicator');
      var tabsContainer = $element.find('.tabs_container');
      
      var lastLeft = 0;
      var attachIndicator = function(element){
        var pos = element.position();
        var left = pos.left + tabsContainer.offset().left;
        var moveRight = lastLeft > pos.left;
        
        var tabOffsetLeft = 0;
        
        lastLeft = pos.left;
        
        if(moveRight && !indicator.hasClass('right')){
          indicator.addClass('right');
          if(indicator.hasClass('left')) indicator.removeClass('left');
        }else if(!moveRight && !indicator.hasClass('left')){
          indicator.addClass('left');
          if(indicator.hasClass('right')) indicator.removeClass('right');
        }
        
        var right = $element.outerWidth() - pos.left - element.outerWidth() - tabOffsetLeft;
          
        if(!moveRight){
          
          indicator.css('left', left)
          indicator.css('right', right);
            
          $timeout(function(){
            indicator.css('left', pos.left);
          }, 10);
        } else {
          left = pos.left;
          
          indicator.css('left', left)
          
          $timeout(function(){
            indicator.css('right', right);
          }, 10);
        };
        
        // fix right side scroll
        var scrollOffset = (pos.left + element.outerWidth()) - $element.outerWidth() + tabOffsetLeft;
        if(scrollOffset > 0)
        {
          tabsContainer.scrollLeft(scrollOffset);
        }
        
        // fix left side scroll
        if(tabsContainer.scrollLeft() > pos.left){
          tabsContainer.scrollLeft(-pos.left);
        }
      };
      
      this.selected = function(tab, apply){
        $scope.selected = tab.data('id');
        tab.focus();
        if(apply) $scope.$apply();
      };
      
      var apply = function(tab){
        
        attachIndicator(tab);
        if($scope.multi === undefined){
          $element.find('tab').each(function(i, e){
            var $e = angular.element(e);
            if($e.attr('aria-selected') !== undefined){
              $e.removeAttr('aria-selected');
              $e.attr('tabindex', -1);
            }
          });
        
          tab.attr('aria-selected', 'true');
          tab.attr('tabindex', 0);
        }
      };
      
      $scope.$watch('selected', function(n, o){
        if(n === undefined || n > self.count() || n < 0) return;
        self.currentTab = n;
        
        apply(tabs[n]);
      });
      
    }
  }
});

app.directive('tab', function(){
  return {
    restrict: 'E',
    require: '^tabLayout',
    template: '<div class="tab-content" ng-transclude>',
    transclude: true,
    link: function($scope, $element, $attrs, ngCtrl){
      $element.attr('role', 'tab');
      
      var disabled = $element.attr('disabled') !== undefined && $element.attr('disabled') !== false;
      if(disabled) return;
      
      // init
      var id = ngCtrl.count();
      
      $element.data('id', id);
      
      if(id === ngCtrl.currentTab){
        ngCtrl.selected($element);
      }
      
      ngCtrl.addTab($element);
      
      applyElementEvents($element, -1);
      
      $element.bind('click', function(){
        ngCtrl.selected($element, true);
      });
    }
  }
});

// helpers
/*
  apply events to elements
*/

const ATTR_PRESSED = 'pressed';
const ATTR_FOCUSED = 'focused';
const ATTR_DISABLED = 'disabled';
const ATTR_AREA_DISABLED = 'aria-disabled';
  
function applyElementEvents($element, tabindex){
  tabindex = tabindex || 0;
  $element.attr('tabindex', tabindex);
  
  var disabled = $element.attr(ATTR_DISABLED) !== undefined && $element.attr(ATTR_DISABLED) !== false;
      
  $element.attr(ATTR_AREA_DISABLED, disabled);
  if(disabled) $element.attr(ATTR_DISABLED, '');
    
  // add pressed attr when mousedown
  $element.bind('mousedown', function(){
    if($element.attr(ATTR_PRESSED) === undefined){
      $element.attr(ATTR_PRESSED, '');
    }
  });
  
  // remove pressed attr when mousedown
  $element.bind('mouseup', function(){
    if($element.attr(ATTR_PRESSED) != undefined)
      $element.removeAttr(ATTR_PRESSED);
  });
  
  // add pressed attr when keybord space and enter key pressed
  $element.bind('keydown', function(event){
    if(event.keyCode === 13 || event.keyCode === 32){ // enter or space
      if($element.attr(ATTR_PRESSED) === undefined){
        $element.attr(ATTR_PRESSED, '');
      }
      
      event.stopPropagation();
      event.preventDefault();
      return;
    }
  });
  
  // remove pressed attr when keybord space and enter key pressed
  $element.bind('keyup', function(event){
    if(event.keyCode === 13 || event.keyCode === 32){ // enter or space
      if($element.attr(ATTR_PRESSED) !== undefined){
        $element.removeAttr(ATTR_PRESSED);
      }
      
      event.stopPropagation();
      event.preventDefault();
      return;
    }
  });
  
  // add focused attr when focus
  $element.bind('focus', function(event){
    if($element.attr(ATTR_FOCUSED) === undefined)
      $element.attr(ATTR_FOCUSED, '');
  });
  
  // remove focused attr when blur
  $element.bind('blur', function(event){
    if($element.attr(ATTR_FOCUSED) !== undefined)
      $element.removeAttr(ATTR_FOCUSED);
  });
};

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js
  3. https://cdnjs.cloudflare.com/ajax/libs/jquery.touchswipe/1.6.18/jquery.touchSwipe.min.js