Tab Anime

Chromeライクな角丸のタブ機能。

詳しい導入事例は導入方法を参照してください。

  • DEMO
  • タブ1
  • タブ2
  • タブ3
表示1
表示2
表示3

使い方

*-li-*に+-.act-+のクラス追加で初期表示可能。

タブボタンは{overflow-x: auto;}しているので超過分はスクロール表示になります。
PCの時はカッコ悪いので注意。その内直す予定です。

スマホの時は表示部分をスワイプでも切替可能です。

見た目的に残しておこうと思ったのと、シンプルなマークアップで使いやすいのでJSは作り込んでいません。
なので通常のTabのようにネストや色々なサーチはできません。
恐らく複数設置も不可かも。

背景色のは初期から入れているので変更したい場合はCSSを書き換えてください。

  • HTML
  • CSS
  • Script
.mod_tabAnime {
  padding: 24px;
  background: #dde3e9;
}
.mod_tabAnime-list {
  padding: 0 16px;
  display: flex;
  gap: 6px;
  overflow-x: auto;
  overflow-y: hidden;
}
.mod_tabAnime-btn {
  padding: 4px 0;
  font-size: var(--typo-min);
  align-self: flex-end;
}
.mod_tabAnime-btn-in {
  min-width: 80px;
  padding: 2px 8px;
  position: relative;
  box-sizing: border-box;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.mod_tabAnime-btn.act {
  border-radius: 6px 6px 0 0;
  background: var(--bg-paper);
  position: relative;
  z-index: 1;
}
.mod_tabAnime-btn.act::before,
.mod_tabAnime-btn.act::after {
  content: '';
  width: 12px;
  height: 6px;
  border-radius: 0 0 6px 6px;
  background: #dde3e9;
  display: block;
  position: absolute;
  bottom: 0;
  z-index: 3;
}
.mod_tabAnime-btn.act::before {left: -12px;}
.mod_tabAnime-btn.act::after {right: -12px;}
.mod_tabAnime-btn.act .mod_tabAnime-btn-in::before,
.mod_tabAnime-btn.act .mod_tabAnime-btn-in::after {
  content: '';
  width: 8px;
  height: 6px;
  background: var(--bg-paper);
  display: block;
  position: absolute;
  bottom: -6px;
  z-index: 2;
}
.mod_tabAnime-btn.act .mod_tabAnime-btn-in::before {left: -8px;}
.mod_tabAnime-btn.act .mod_tabAnime-btn-in::after {right: -8px;}
.mod_tabAnime-btn:not(.act) .mod_tabAnime-btn-in {
  width: 100%;
  border-radius: 6px;
  top: -2px;
  transition: .2s;
  cursor: pointer;
}
.mod_tabAnime-btn:not(.act) .mod_tabAnime-btn-in:hover {
  background: rgb(0 0 0 / .08);
  z-index: 2;
}

.mod_tabAnime-sec {
  padding: 16px;
  border-radius: 6px;
  background: var(--bg-paper);
  display: flex;
  gap: 16px;
  overflow: hidden;
}
.mod_tabAnime-item {
  min-width: 100%;
  transition: .2s;
}
@media screen and (max-width: 768px) {
  .mod_tabAnime {padding: 24px 16px;}
}
var isTouch = ('ontouchstart' in window);
$('.mod_tabAnime').each(function(){
  let $list = $('.mod_tabAnime-list',this);
  let $btn = $list.children();
  let $sec = $('.mod_tabAnime-sec',this);
  let $itm = $sec.children();

  $btn.addClass('mod_tabAnime-btn').wrapInner('<div class="mod_tabAnime-btn-in"></div>');
  $itm.addClass('mod_tabAnime-item');
  $sec.show();

  //初期表示
  if($list.children('.act')[0]){
    let i = $list.children('.mod_tabAnime-btn.act').index();
    let mgn = 0;
    if(i != 0){mgn = 16*i}
    $itm.css('transform','translateX(calc(-'+ i +'00% - '+mgn+'px))');
    $sec.height($('.mod_tabAnime-item').eq(i).children().height());
  } else {
    let $t = $btn.eq(0);
    $t.addClass('act');
  }
});
$bd.on('click','.mod_tabAnime-btn-in',function(){
  if($(this).hasClass('act')){return false;}

  let $btn = $(this).parent();
  let $Mod = $(this).parents('.mod_tabAnime');
  let $sec = $Mod.find('.mod_tabAnime-sec');
  let $itm = $Mod.find('.mod_tabAnime-item');
  let i = $btn.index();
  let mgn = 0;
  if(i != 0){mgn = 16*i}

  $btn.addClass('act').siblings().removeClass('act');
  $itm.css('transform','translateX(calc(-'+ i +'00% - '+mgn+'px))');
  $sec.height($itm.eq(i).children().height());
});
$('.mod_tabAnime-sec').bind({
  'touchstart': function(e){
    this.pageX = (isTouch ? event.changedTouches[0].pageX : e.pageX);
    this.pageY = (isTouch ? event.changedTouches[0].pageY : e.pageY);
    this.touchedTabSec = true;
    this.touched = false;
  },
  'touchmove': function(e){
    if(!this.touchedTabSec){return;}
    this.pageXm = (isTouch ? event.changedTouches[0].pageX : e.pageX);
    this.pageYm = (isTouch ? event.changedTouches[0].pageY : e.pageY);

    if(this.pageXm != 0){
      let i = $('.mod_tabAnime-btn.act').index();
      if(this.pageXm - this.pageX > 0 && i == 0 || this.pageXm - this.pageX < 0 && i + 1 == $('.mod_tabAnime-btn').length){
        this.touchedTabSec = false;
        return;
      }
      if(i == 0){var X = 0;}
      else {var X = -(i * 100);}
      let yc = this.pageXm - this.pageX + 1 + X;
      $('.mod_tabAnime-item').css({
        'transform':'translateX(' + yc + '%)',
        'transition':'0.1s'
      });
      nowTouch = true;
    }
  },
  'touchend': function(e){
    if(!this.touchedTabSec){return;}
    this.touchedTabSec = false;
    this.left = this.pageX - (isTouch ? event.changedTouches[0].pageX : e.pageX);
    this.top = this.pageY - (isTouch ? event.changedTouches[0].pageY : e.pageY);

    if(this.top == 0 && this.left == 0){return;}

    let $btn = $('.mod_tabAnime-btn');
    let $act = $('.mod_tabAnime-btn.act');
    let $itm = $('.mod_tabAnime-item');
    let i = $act.index();
    let mgn = 16;
    let Xm = 100;

    if(this.left > 24){// NEXT Move
      if(i != 0){
        Xm = i + 1 + '00';
        mgn = 16 * (i + 1);
      }

      $itm.css('transform','translateX(calc(-' + Xm + '% - '+mgn+'px))');
      $act.removeClass('act');
      $btn.eq(i + 1).addClass('act');
      $('.mod_tabAnime-sec').height($itm.eq(i + 1).children().height());
      nowTouch = false;

    } else if(this.left < -24){// PREV  Move
      if(i != 0){
        Xm = i - 1 + '00';
        mgn = 16 * (i - 1);
      }

      $itm.css('transform','translateX(calc(-' + Xm + '% - '+mgn+'px))');
      $act.removeClass('act');
      $btn.eq(i - 1).addClass('act');
      $('.mod_tabAnime-sec').height($itm.eq(i - 1).children().height());
      nowTouch = false;

    } else {// BACK Move
      let tX = 0;
      if(i != 0){tX = '-' + i + '00%';}

      $itm.css({
        'transform':'translateX(' + tX + ')',
        'transition':'0.1s'
      });
    }
  }
});