250x250
Notice
Recent Posts
Recent Comments
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

진스

Vue Swipe Tabs 구현 본문

Vue

Vue Swipe Tabs 구현

입방정 2021. 5. 16. 23:58
728x90

탭을 스와이프해서 넘기는 것을 구현하고자 한다.

vuetify 라이브러리를 사용 할수도 있지만 세부 커스터 마이징이 어려워서 라이브러리를 쓰진 않았다.

방법1.

HTML

<!-- TAB content -->
<div v-on="pointer
?{pointerdown: ($event)=>start($event), pointermove: ($event)=>move($event), pointerup: ($event)=>end($event)}
:{touchstart: ($event)=>start($event), touchmove: ($event)=>move($event), touchend: ($event)=>end($event)}"
style="touch-action: pan-y;"
>
  <transition :name="transition" v-for="(tab, idx) in tabs" :key="idx">
  	<div v-if="idx === activetab">

- 이벤트 캐치할 div

- 탭의 개수만큼 Transition 생성

- 선택된 탭만 보이도록 하위 div에서 v-if 조건으로 설정한다.

 

 

JS

/* TAB 관련 Method */
    switchtab (n) {
      if (this.activetab > n) {
        this.transition = 'slide-prev'
      } else if (this.activetab < n) {
        this.transition = 'slide-next'
      }
    },
    start (e) {
      this.settouchpos(e, 'start')
    },
    move (e) {
      this.settouchpos(e, 'move')
    },
    end (e) {
      this.settouchpos(e, 'end')
      let dx = this.touch.sx - this.touch.ex,
        dy = this.touch.sy - this.touch.ey,
        dt = this.touch.et - this.touch.st,
        dir = Math.sign(dx),
        ntab = this.activetab + dir,
        vmove = Math.abs(dx) / Math.abs(dy) < Math.sqrt(4)
      ntab = ntab >= 0 && ntab < this.tabs.length ? ntab : null
      if (Math.abs(dx) > 10 && ntab !== null && !vmove && dt < 300) this.switchtab(ntab)
    },
    settouchpos (e, event) {
      let ev = e.changedTouches ? e.changedTouches[0] : e
      if (event === 'start') {
        this.touch.sx = ev.clientX
        this.touch.sy = ev.clientY
        this.touch.st = Date.now()
      } else {
        this.touch.ex = ev.clientX
        this.touch.ey = ev.clientY
        this.touch.et = Date.now()
      }
    }
  

 

- Switchtab : 탭 변경함수

- Start : 터치 시작

- Move : 터치 이동

- End : 터치 끝 ( 스와이프 방향에 따라 switchtab 함수 호출)

- Settouchpos : start, end 좌표를 기록하는 함수

 

 

CSS

.slide-next-leave-active, .slide-next-enter-active, .slide-prev-enter-active, .slide-prev-leave-active {
  transition: .3s;
}
.slide-next-enter,.slide-next-leave, .slide-prev-leave-to
{
  transform: translate(100%, 0);
}
.slide-next-leave-to, .slide-prev-enter, .slide-prev-leave {
  transform: translate(-100%, 0);
}

 

- transition : swipe 속도 조절

- transform : 탭나타나고 사라지는 애니메이션 효과

 

 

 

 

탭2 - CodeSandbox

탭2 by baejm using @vue/cli-plugin-babel, vue

codesandbox.io

최종

<template>
  <div id="app">
    <div class="tabs" ref="tabbar">
      <div
        class="tabitem"
        :class="index === activetab ? 'active' : ''"
        v-for="(tab, index) in items"
        @click="switchtab(index)"
        :key="index"
        ref="tab"
      >
        {{ tab }}
      </div>
      <div
        class="slider"
        :style="'transform:translateX(' + activetab * tabwidth + 'px)'"
      ></div>
    </div>

    <div
      ref="tcon"
      class="tabcontainer"
      v-on="
        pointer
          ? {
              pointerdown: ($event) => start($event),
              pointermove: ($event) => move($event),
              pointerup: ($event) => end($event),
            }
          : {
              touchstart: ($event) => start($event),
              touchmove: ($event) => move($event),
              touchend: ($event) => end($event),
            }
      "
    >
      <transition :name="transition" v-for="(tab, index) in items" :key="index">
        <div class="tabpane" v-if="index === activetab">
          {{ tab }} : Tab Content
        </div>
      </transition>
    </div>
    <h2>Source Code :</h2>
    <a
      href="https://gist.github.com/dagalti/eea7f49378d9fa21de5e359dde0f859a"
      target="blank"
      >Vue Simple Swipable Material Tabs</a
    >
  </div>
</template>

<script>
export default {
  data() {
    return {
      transition: "slide-next",
      activetab: 0,
      tabwidth: 135,
      items: [
        "Google",
        "Apple",
        "Microsoft",
        "Samsung",
        "Nokia",
        "Sony",
        "Vivo",
        "Oneplus",
        "Oppo",
        "LG",
        "Blackberry",
      ],
      touch: { sx: null, sy: null, st: null, ex: null, ey: null, et: null },
    };
  },
  mounted() {
    this.$refs.tabbar.style.setProperty("--tabwidth", this.tabwidth + "px");
  },
  computed: {
    pointer() {
      if (window.PointerEvent) return true;
      else return false;
    },
  },
  methods: {
    switchtab(n) {
      let scroll, scond;
      if (this.activetab > n) {
        this.transition = "slide-prev";
        scroll = n - 1;
        if (scond)
          this.$refs.tab[scroll].scrollIntoView({ behavior: "smooth" });
      } else if (this.activetab < n) {
        this.transition = "slide-next";
        scroll = n + 1;
      }
      scond = scroll >= 0 && scroll < this.items.length;
      if (scond) this.$refs.tab[scroll].scrollIntoView({ behavior: "smooth" });

      this.$nextTick((_) => {
        this.activetab = n;
      });
    },
    start(e) {
      this.settouchpos(e, "start");
    },
    move(e) {
      this.settouchpos(e, "move");
    },
    end(e) {
      this.settouchpos(e, "end");
      let dx = this.touch.sx - this.touch.ex,
        dy = this.touch.sy - this.touch.ey,
        dt = this.touch.et - this.touch.st,
        dir = Math.sign(dx),
        ntab = this.activetab + dir,
        vmove = Math.abs(dx) / Math.abs(dy) < Math.sqrt(4);
      ntab = ntab >= 0 && ntab < this.items.length ? ntab : null;
      if (Math.abs(dx) > 10 && ntab !== null && !vmove && dt < 300)
        this.switchtab(ntab);
    },
    settouchpos(e, event) {
      let ev = e.changedTouches ? e.changedTouches[0] : e;
      if (event === "start") {
        this.touch.sx = ev.clientX;
        this.touch.sy = ev.clientY;
        this.touch.st = Date.now();
      } else {
        this.touch.ex = ev.clientX;
        this.touch.ey = ev.clientY;
        this.touch.et = Date.now();
      }
    },
  },
};
</script>


<style >
body {
  font-family: "Roboto", Helvetica, sans-serif;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  margin: 0;
}
.slide-next-leave-active,
.slide-next-enter-active,
.slide-prev-enter-active,
.slide-prev-leave-active {
  transition: 0.5s;
}
.slide-next-enter,
.slide-next-leave,
.slide-prev-leave-to {
  transform: translate(100%, 0);
}

.slide-next-leave-to,
.slide-prev-enter,
.slide-prev-leave {
  transform: translate(-100%, 0);
}

.tabs {
  display: flex;
  position: relative;
  background: #1565c0;
  color: #f1f1f1;
  height: 48px;
  box-shadow: 0 3px 3px -2px rgba(0, 0, 0, 0.2), 0 3px 4px 0 rgba(0, 0, 0, 0.14),
    0 1px 7px 0 rgba(0, 0, 0, 0.12);
  overflow-x: scroll;
  overflow: -moz-scrollbars-none;
  -ms-overflow-style: none;
}
.tabs::-webkit-scrollbar {
  width: 0 !important;
  height: 0 !important;
}
.tabitem {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tabwidth);
  cursor: pointer;
  text-transform: uppercase;
  font-size: 14px;
}
.tabitem.active {
  font-weight: 500;
  color: white;
}
.slider {
  position: absolute;
  bottom: 0px;
  height: 2px;
  width: var(--tabwidth);
  background: white;
  transition: 0.5s ease;
}

.tabcontainer {
  height: 500px;
  position: relative;
  min-height: 100%;
  width: 100%;
  touch-action: pan-y;
}

.tabpane {
  position: absolute;
  width: 100%;
  font-size: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 300px;
}

#app {
  margin: 0 auto;
  text-align: center;
}
</style>



출처:

 

Vue Swipe Tabs 구현

탭을 스와이프해서 넘기는 것을 구현하고자 한다. vuetify 라이브러리를 사용 할수도 있지만 세부 커스터 마이징이 어려워서 라이브러리를 쓰진 않았다. HTML start($event), pointermove: ($event)=>move($event)

raon0229.tistory.com

 

 


방법2.

 

 

탭1 - CodeSandbox

탭1 by baejm using @vue/cli-plugin-babel, vue

codesandbox.io

index.vue

<template>
  <div class="example">
    <div class="tabs">
      <TabItem
        v-for="item in list"
        v-bind="item" :key="item.id"
        v-model="currentId"/>
    </div>
    <div class="contents">
      <transition>
        <section class="item" :key="currentId">
          {{ current.content }}
        </section>
      </transition>
    </div>
  </div>
</template>

<script>
import TabItem from './TabItem.vue'
export default {
  components: { TabItem },
  data() {
    return {
      currentId: 1,
      list: [
        { id: 1, label: 'Tab1', content: '콘텐츠1' },
        { id: 2, label: 'Tab2', content: '콘텐츠2' },
        { id: 3, label: 'Tab3', content: '콘텐츠3' }
      ]
    }
  },
  computed: {
    current() {
      return this.list.find(el => el.id === this.currentId) || {}
    }
  }
}
</script>

<style scoped>
.contents {
  position: relative;
  overflow: hidden;
  width: 280px;
  border: 2px solid #000;
}
.item {
  box-sizing: border-box;
  padding: 10px;
  width: 100%;
  transition: all 0.8s ease;
}
/* 트랜지션 전용 스타일 */
.v-leave-active {
  position: absolute;
}
.v-enter {
  transform: translateX(-100%);
}
.v-leave-to {
  transform: translateX(100%);
}
</style>

 

tab.vue

<template>
  <button @click="$emit('input', id)" :class="[active, 'tab']">
    {{ label }}
  </button>
</template>

<script>
export default {
  props: {
    id: Number,
    label: String,
    value: Number
  },
  computed: {
    active() {
      return this.value === this.id ? 'active' : false
    }
  }
}
</script>

<style scoped>
.tab {
  border-radius: 2px 2px 0 0;
  background: #fff;
  color: #311d0a;
  line-height: 24px;
}
.tab:hover {
  background: #eeeeee;
}
.active {
  background: #f7c9c9;
}
</style>

 

출처

 

 

고양이도 할 수 있는 Vue.js

탭 데모 단순하면 재미가 없으므로 트랜지션을 사용해서 슬라이드하도록 만들었습니다. 사용하고 있는 주요 기능 클래스 데이터 바인딩하기 62페이지 여러 속성 데이터 바인딩하기 64페이지 산

rintiantta.github.io

 

728x90

'Vue' 카테고리의 다른 글

Vue/Nuxt Element 높이 값 받아오기  (0) 2021.05.21
vue 페이지 이동시 input 창 자동 포커스  (2) 2021.05.19
mousemove  (0) 2021.05.16
input에 1byte마다 자음마다 validation  (0) 2021.05.14
vue 개발 확장 플러그인  (0) 2021.05.13
Comments