정보실

웹학교

정보실

javascript Tabs Navigation UI

본문

Natalia Davydova는 탭을 전환 할 때 적절한 양의 미묘한 애니메이션으로 깔끔한 탭 UI를 공유합니다.


https://codepen.io/nat-davydova/pen/PMJJRM 


HTML



<!--PEN HEADER-->
<div class="header">
  <h1 class="header__title">Tabs Navigation UI</h1>
  <div class="header__btns btns"><a class="header__btn btn" href="https://github.com/nat-davydova/tab-navigation" title="Check on Github" target="_blanc">Check on Github</a></div>
</div>
<!--PEN CONTENT-->
<div class="content">
  <!--content title-->
  <h2 class="content__title">Click to any navigation link</h2>
  <!--content inner-->
  <div class="content__inner">
    <!--tabs-->
    <div class="tabs">
      <!--tabs navigation-->
      <div class="tabs__nav">
        <ul class="tabs__nav-list">
          <li class="tabs__nav-item js-active">Profile</li>
          <li class="tabs__nav-item">Settings</li>
          <li class="tabs__nav-item">About Us</li>
          <li class="tabs__nav-item">FAQ</li>
        </ul>
      </div>
      <!--tabs panels-->
      <div class="tabs__panels">
        <!--single panel-->
        <div class="tabs__panel">
          <div class="tabs__panel-card">
            <div class="tabs__panel-avatar"></div>
            <div class="tabs__panel-content"></div>
          </div>
          <div class="tabs__panel-card">
            <div class="tabs__panel-content"></div>
          </div>
          <div class="tabs__panel-card">
            <div class="tabs__panel-content"></div>
          </div>
        </div>
        <!--single panel-->
        <div class="tabs__panel">
          <div class="tabs__panel-card">
            <div class="tabs__panel-content"></div>
            <div class="tabs__panel-content"></div>
          </div>
          <div class="tabs__panel-card">
            <div class="tabs__panel-content"></div>
          </div>
          <div class="tabs__panel-card tabs__panel-card--spaced-between">
            <div class="tabs__panel-img"></div>
            <div class="tabs__panel-img"></div>
            <div class="tabs__panel-img"></div>
          </div>
        </div>
        <!--single panel-->
        <div class="tabs__panel">
          <div class="tabs__panel-card">
            <div class="tabs__panel-content"></div>
          </div>
          <div class="tabs__panel-card">
            <div class="tabs__panel-content"></div>
            <div class="tabs__panel-img"></div>
          </div>
          <div class="tabs__panel-card">
            <div class="tabs__panel-img"></div>
            <div class="tabs__panel-content"></div>
          </div>
        </div>
        <!--single panel-->
        <div class="tabs__panel">
          <div class="tabs__panel-card">
            <div class="tabs__panel-content"></div>
            <div class="tabs__panel-content"></div>
          </div>
          <div class="tabs__panel-card">
            <div class="tabs__panel-content"></div>
          </div>
          <div class="tabs__panel-card">
            <div class="tabs__panel-content"></div>
            <div class="tabs__panel-content"></div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>


CSS


* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: 'Lato', sans-serif;
  font-size: 16px;
  color: #2c2c2c;
}
body a {
  color: inherit;
  text-decoration: none;
}

.btn {
  transition-property: all;
  transition-duration: 0.2s;
  transition-timing-function: linear;
  transition-delay: 0s;
  padding: 10px 20px;
  margin-right: 10px;
  background-color: #fff;
  border: 1px solid #2c2c2c;
  border-radius: 3px;
  cursor: pointer;
  outline: none;
}
.btn:last-child {
  margin-right: 0;
}
.btn:hover, .btn.js-active {
  color: #fff;
  background-color: #2c2c2c;
}

.header {
  max-width: 500px;
  margin: 50px auto;
  text-align: center;
}

.header__title {
  margin-bottom: 30px;
  font-weight: 500;
}

.content {
  max-width: 700px;
  margin: auto;
}

.content__title {
  margin-bottom: 20px;
  font-size: 18px;
  font-weight: 500;
  text-align: center;
}

.content__inner {
  width: 375px;
  height: 550px;
  margin: auto;
  box-shadow: 0 8px 17px 2px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12), 0 5px 5px -3px rgba(0, 0, 0, 0.2);
}

.tabs {
  position: relative;
  padding: 15px;
  height: 100%;
  overflow: hidden;
}

.tabs__nav {
  position: relative;
}

.tabs__nav-decoration {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  transition: width .2s linear 0s, -webkit-transform .2s ease-out 0s;
  transition: width .2s linear 0s, transform .2s ease-out 0s;
  transition: width .2s linear 0s, transform .2s ease-out 0s, -webkit-transform .2s ease-out 0s;
  background-color: #119DA4;
  border-radius: 3px;
  z-index: 1;
}

.tabs__nav-list {
  position: relative;
  display: flex;
  justify-content: space-between;
  list-style-type: none;
  z-index: 5;
}

.tabs__nav-item {
  transition-property: all;
  transition-duration: 0.2s;
  transition-timing-function: linear;
  transition-delay: 0s;
  padding: 15px;
  cursor: pointer;
}
.tabs__nav-item.js-active {
  transition-property: all;
  transition-duration: 0.2s;
  transition-timing-function: linear;
  transition-delay: 0.05s;
  color: #fff;
}

.tabs__panels {
  position: relative;
  margin-top: 30px;
}

.tabs__panel {
  position: absolute;
  top: 0;
  left: 0;
  transition: none;
  -webkit-transform: scale(0.8);
          transform: scale(0.8);
  width: 100%;
  opacity: 0;
}
.tabs__panel.js-active {
  transition: all .25s linear 0s;
  -webkit-transform: scale(1);
          transform: scale(1);
  opacity: 1;
}

.tabs__panel-card {
  display: flex;
  margin-bottom: 30px;
  padding: 15px;
  box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.12), 0 1px 5px 0 rgba(0, 0, 0, 0.2);
}
.tabs__panel-card:last-child {
  margin-bottom: 0;
}

.tabs__panel-card--spaced-between {
  justify-content: space-between;
}

.tabs__panel-avatar {
  flex-shrink: 0;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  background-color: rgba(0, 0, 0, 0.15);
}

.tabs__panel-img {
  flex-shrink: 0;
  width: 80px;
  height: 80px;
  border-radius: 4px;
  background-color: rgba(0, 0, 0, 0.15);
}

.tabs__panel-content {
  width: 100%;
  margin-left: 30px;
}
.tabs__panel-content:first-child {
  margin-left: 0;
}
.tabs__panel-content:not(:last-child) {
  margin-right: 30px;
}
.tabs__panel-content:before, .tabs__panel-content:after {
  display: block;
  width: 100%;
  height: 20px;
  content: '';
  background-color: rgba(0, 0, 0, 0.15);
}
.tabs__panel-content:before {
  margin-bottom: 15px;
}


Javascript


const DOM = {
  tabsNav: document.querySelector('.tabs__nav'),
  tabsNavItems: document.querySelectorAll('.tabs__nav-item'),
  panels: document.querySelectorAll('.tabs__panel') };


//set active nav element
const setActiveItem = elem => {

  DOM.tabsNavItems.forEach(el => {

    el.classList.remove('js-active');

  });

  elem.classList.add('js-active');

};

//find active nav element
const findActiveItem = () => {

  let activeIndex = 0;

  for (let i = 0; i < DOM.tabsNavItems.length; i++) {if (window.CP.shouldStopExecution(0)) break;

    if (DOM.tabsNavItems[i].classList.contains('js-active')) {
      activeIndex = i;
      break;
    };

  }window.CP.exitedLoop(0);;

  return activeIndex;

};

//find active nav elements parameters: left coord, width
const findActiveItemParams = activeItemIndex => {

  const activeTab = DOM.tabsNavItems[activeItemIndex];

  //width of elem
  const activeItemWidth = activeTab.offsetWidth - 1;

  //left coord in the tab navigation
  const activeItemOffset_left = activeTab.offsetLeft;

  return [activeItemWidth, activeItemOffset_left];

};

//appending decoration block to an active nav element
const appendDecorationNav = () => {

  //creating decoration element
  let decorationElem = document.createElement('div');

  decorationElem.classList.add('tabs__nav-decoration');
  decorationElem.classList.add('js-decoration');

  //appending decoration element to navigation
  DOM.tabsNav.append(decorationElem);

  //appending styles to decoration element
  return decorationElem;
};

//appending styles to decoration nav element
const styleDecorElem = (elem, decorWidth, decorOffset) => {
  elem.style.width = `${decorWidth}px`;
  elem.style.transform = `translateX(${decorOffset}px)`;
};

//find active panel
const findActivePanel = index => {

  return DOM.panels[index];

};

//set active panel class
const setActivePanel = index => {

  DOM.panels.forEach(el => {

    el.classList.remove('js-active');

  });

  DOM.panels[index].classList.add('js-active');

};

//onload function
window.addEventListener('load', () => {

  //find active nav item
  const activeItemIndex = findActiveItem();

  //find active nav item params
  const [decorWidth, decorOffset] = findActiveItemParams(activeItemIndex);

  //appending decoration element to an active elem
  const decorElem = appendDecorationNav();

  //setting styles to the decoration elem
  styleDecorElem(decorElem, decorWidth, decorOffset);

  //find active panel
  findActivePanel(activeItemIndex);

  //set active panel
  setActivePanel(activeItemIndex);
});

//click nav item function
DOM.tabsNav.addEventListener('click', e => {

  const navElemClass = 'tabs__nav-item';

  //check if we click on a nav item
  if (e.target.classList.contains(navElemClass)) {

    const clickedTab = e.target;

    const activeItemIndex = Array.from(DOM.tabsNavItems).indexOf(clickedTab);

    //set active nav item
    setActiveItem(clickedTab);

    //find active nav item
    const activeItem = findActiveItem();

    //find active nav item params
    const [decorWidth, decorOffset] = findActiveItemParams(activeItem);

    //setting styles to the decoration elem
    const decorElem = document.querySelector('.js-decoration');

    styleDecorElem(decorElem, decorWidth, decorOffset);

    //find active panel
    findActivePanel(activeItemIndex);

    //set active panel
    setActivePanel(activeItemIndex);

  }

});




페이지 정보

조회 35회 ]  작성일19-08-14 16:51

웹학교