vue 기반 탐색 슬라이딩 구성 요소 기능 구현

머리말
어이,탐정 이 라면 여러분 의 프로그램 왕 이 낯 설 지 않 을 것 입 니 다.(여동생 이 많 으 니까)위 에서 미 끄 러 운 간판 을 뒤 집 을 수 있 습 니 다.탐정 의 중첩 미끄럼 구성 요 소 는 관건 적 인 역할 을 했 습 니 다.다음은 vue 로 탐정 의 중첩 구성 요 소 를 어떻게 쓰 는 지 살 펴 보 겠 습 니 다.
기능 분석
간단하게 아래 탐색 기 를 사용 하면 중첩 미끄럼 의 기능 이 매우 간단 하 다 는 것 을 알 수 있다.한 장의 그림 으로 요약 하면:

간단하게 요약 하면 안에 포 함 된 기본 기능 점:
  • 그림 의 중첩
  • 사진 첫 번 째 미끄럼
  • 조건 성공 후 슬라이딩,조건 실패 후 리 턴
  • 미 끄 러 진 후 다음 그림 이 꼭대기 까지 쌓 여 있다.
  • 체험 최적화
    터치 점 에 따라 미 끄 러 질 때 첫 번 째 그림 은 각도 가 다 릅 니 다.
    오프셋 면적 은 성공 적 으로 미 끄 러 졌 는 지 여 부 를 판정 합 니 다.
    구체 적 실현
    귀납 적 인 기능 점 이 있 으 면 우 리 는 구성 요 소 를 실현 하 는 사고 가 더욱 뚜렷 해 질 것 이다.
    1.중첩 효과
    중첩 이미지 효 과 는 인터넷 에서 대량의 사례 가 있 고 실현 하 는 방법 은 대동소이 하 다.주로 부모 층 에 perspective 와 perspective-origin 을 설정 하여 서브 층 의 투시 를 실현 한다.서브 층 은 translate3d Z 축 수 치 를 설정 하면 중첩 효 과 를 모 의 할 수 있다.구체 적 인 코드 는 다음 과 같다.
    
    //     dom
     <!--opacity: 0          stack-item  -->
     <!--z-index: -1   stack-item  "-->
    <ul class="stack">
     <li class="stack-item" style="transform: translate3d(0px, 0px, 0px);opacity: 1;z-index: 10;"><img src="1.png" alt="01"></li>
     <li class="stack-item" style="transform: translate3d(0px, 0px, -60px);opacity: 1;z-index: 1"><img src="2.png" alt="02"></li>
     <li class="stack-item" style="transform: translate3d(0px, 0px, -120px);opacity: 1;z-index: 1"><img src="3.png" alt="03"></li>
     <li class="stack-item" style="transform: translate3d(0px, 0px, -180px);opacity: 0;z-index: -1"><img src="4.png" alt="04"></li>
     <li class="stack-item" style="transform: translate3d(0px, 0px, -180px);opacity: 0;z-index: -1"><img src="5.png" alt="05"></li>
    </ul>
    <style>
    .stack {
      width: 100%;
      height: 100%;
      position: relative;
      perspective: 1000px; //     
      perspective-origin: 50% 150%; //       
      -webkit-perspective: 1000px;
      -webkit-perspective-origin: 50% 150%;
      margin: 0;
      padding: 0;
     }
     .stack-item{
      background: #fff;
      height: 100%;
      width: 100%;
      border-radius: 4px;
      text-align: center;
      overflow: hidden;
     }
     .stack-item img {
      width: 100%;
      display: block;
      pointer-events: none;
     }
    </style>
    위 는 정적 코드 일 뿐 입 니 다.vue 구성 요 소 를 원 하기 때문에 먼저 구성 요소 템 플 릿 stack.vue 를 만들어 야 합 니 다.템 플 릿 에서 우 리 는 v-for 를 사용 하여 stack 노드 를 옮 겨 다 닐 수 있 습 니 다.사용:style 로 각 item 의 style 을 수정 할 수 있 습 니 다.코드 는 다음 과 같 습 니 다.
    
    <template>
      <ul class="stack">
       <li class="stack-item" v-for="(item, index) in pages" :style="[transform(index)]">
        <img :src="item.src">
       </li>
      </ul>
    </template>
    <script>
    export default {
     props: {
      // pages           
      pages: {
       type: Array,
       default: []
      }
     },
     data () {
      return {
       // basicdata          
       basicdata: {
        currentPage: 0 //        
       },
       // temporaryData          
       temporaryData: {
        opacity: 1, //   opacity
        zIndex: 10, //   zIndex
        visible: 3 //          visible
       }
      }
     },
     methods: {
      //     
      transform (index) {
       if (index >= this.basicdata.currentPage) {
        let style = {}
        let visible = this.temporaryData.visible
        let perIndex = index - this.basicdata.currentPage
        // visible          
        if (index <= this.basicdata.currentPage + visible - 1) {
         style['opacity'] = '1'
         style['transform'] = 'translate3D(0,0,' + -1 * perIndex * 60 + 'px' + ')'
         style['zIndex'] = visible - index + this.basicdata.currentPage
         style['transitionTimingFunction'] = 'ease'
         style['transitionDuration'] = 300 + 'ms'
        } else {
         style['zIndex'] = '-1'
         style['transform'] = 'translate3D(0,0,' + -1 * visible * 60 + 'px' + ')'
        }
        return style
       }
      }
     }
    }
    </script>
    포인트
    style 은 대상 을 연결 하 는 동시에 배열 과 함 수 를 연결 할 수 있 습 니 다.이것 은 옮 겨 다 닐 때 유용 합 니 다.
    가장 기본 적 인 dom 구조 가 구축 되 었 습 니 다.다음 단 계 는 첫 번 째 그림 을 움 직 이 는 것 입 니 다.
    2.그림 슬라이딩
    그림 의 미끄럼 효 과 는 많은 장면 에서 나타 나 는데 그 원 리 는 터치 사건 을 감청 하고 위 치 를 바 꾸 는 것 이 아니 라 translate3D 를 통 해 목표 의 위 치 를 바 꾸 는 것 이다.따라서 우리 가 실현 해 야 할 절 차 는 다음 과 같다.
  • stack 에 대해 touchs 사건 의 귀속
  • 제스처 위치 변 화 를 감청 하고 저장 하 는 수치
  • 첫 번 째 그림 css 속성 중 translate3D 의 x,y 값 변경
  • 구체 적 실현
    vue 프레임 워 크 에서 노드 를 직접 조작 하 는 것 을 권장 하지 않 고 명령 v-on 을 통 해 요 소 를 연결 하 는 것 을 권장 합 니 다.따라서 저 희 는 바 인 딩 을 v-for 에 기록 하고 index 를 통 해 첫 번 째 그림 인지 아 닌 지 를 판단 한 다음 에 사용 합 니 다.style 은 첫 페이지 의 스타일 을 수정 합 니 다.구체 적 인 코드 는 다음 과 같 습 니 다.
    
    <template>
      <ul class="stack">
       <li class="stack-item" v-for="(item, index) in pages"
       :style="[transformIndex(index),transform(index)]"
       @touchstart.stop.capture="touchstart"
       @touchmove.stop.capture="touchmove"
       @touchend.stop.capture="touchend"
       @mousedown.stop.capture="touchstart"
       @mouseup.stop.capture="touchend"
       @mousemove.stop.capture="touchmove">
        <img :src="item.src">
       </li>
      </ul>
    </template>
    <script>
    export default {
     props: {
      // pages           
      pages: {
       type: Array,
       default: []
      }
     },
     data () {
      return {
       // basicdata          
       basicdata: {
        start: {}, //       
        end: {}, //       
        currentPage: 0 //        
       },
       // temporaryData          
       temporaryData: {
        poswidth: '', //     
        posheight: '', //     
        tracking: false //      ,      ,    
       }
      }
     },
     methods: {
      touchstart (e) {
       if (this.temporaryData.tracking) {
        return
       }
       //    touch
       if (e.type === 'touchstart') {
        if (e.touches.length > 1) {
         this.temporaryData.tracking = false
         return
        } else {
         //       
         this.basicdata.start.t = new Date().getTime()
         this.basicdata.start.x = e.targetTouches[0].clientX
         this.basicdata.start.y = e.targetTouches[0].clientY
         this.basicdata.end.x = e.targetTouches[0].clientX
         this.basicdata.end.y = e.targetTouches[0].clientY
        }
       // pc  
       } else {
        this.basicdata.start.t = new Date().getTime()
        this.basicdata.start.x = e.clientX
        this.basicdata.start.y = e.clientY
        this.basicdata.end.x = e.clientX
        this.basicdata.end.y = e.clientY
       }
       this.temporaryData.tracking = true
      },
      touchmove (e) {
       //       
       if (this.temporaryData.tracking && !this.temporaryData.animation) {
        if (e.type === 'touchmove') {
         this.basicdata.end.x = e.targetTouches[0].clientX
         this.basicdata.end.y = e.targetTouches[0].clientY
        } else {
         this.basicdata.end.x = e.clientX
         this.basicdata.end.y = e.clientY
        }
        //      
        this.temporaryData.poswidth = this.basicdata.end.x - this.basicdata.start.x
        this.temporaryData.posheight = this.basicdata.end.y - this.basicdata.start.y
       }
      },
      touchend (e) {
       this.temporaryData.tracking = false
       //     ,    
      },
      //        
      transform (index) {
       if (index > this.basicdata.currentPage) {
        let style = {}
        let visible = 3
        let perIndex = index - this.basicdata.currentPage
        // visible          
        if (index <= this.basicdata.currentPage + visible - 1) {
         style['opacity'] = '1'
         style['transform'] = 'translate3D(0,0,' + -1 * perIndex * 60 + 'px' + ')'
         style['zIndex'] = visible - index + this.basicdata.currentPage
         style['transitionTimingFunction'] = 'ease'
         style['transitionDuration'] = 300 + 'ms'
        } else {
         style['zIndex'] = '-1'
         style['transform'] = 'translate3D(0,0,' + -1 * visible * 60 + 'px' + ')'
        }
        return style
       }
      },
      //       
      transformIndex (index) {
       //   3D  
       if (index === this.basicdata.currentPage) {
        let style = {}
        style['transform'] = 'translate3D(' + this.temporaryData.poswidth + 'px' + ',' + this.temporaryData.posheight + 'px' + ',0px)'
        style['opacity'] = 1
        style['zIndex'] = 10
        return style
       }
      }
     }
    }
    </script>
    
    3.조건 성공 후 슬라이딩,조건 실패 후 리 턴
    조건 의 트리거 판단 은 touch end/mouseup 후에 진행 되 며,여기 서 우 리 는 먼저 간단 한 조건 으로 판정 하 는 동시에 첫 번 째 그림 의 팝 업 과 리 턴 효 과 를 줍 니 다.코드 는 다음 과 같 습 니 다.
    
    <template>
      <ul class="stack">
       <li class="stack-item" v-for="(item, index) in pages"
       :style="[transformIndex(index),transform(index)]"
       @touchmove.stop.capture="touchmove"
       @touchstart.stop.capture="touchstart"
       @touchend.stop.capture="touchend"
       @mousedown.stop.capture="touchstart"
       @mouseup.stop.capture="touchend"
       @mousemove.stop.capture="touchmove">
        <img :src="item.src">
       </li>
      </ul>
    </template>
    <script>
    export default {
     props: {
       // pages           
      pages: {
       type: Array,
       default: []
      }
     },
     data () {
      return {
       // basicdata          
       basicdata: {
        start: {}, //       
        end: {}, //       
        currentPage: 0 //        
       },
       // temporaryData          
       temporaryData: {
        poswidth: '', //     
        posheight: '', //     
        tracking: false, //      ,      ,    
        animation: false, //           ,    
        opacity: 1 //        
       }
      }
     },
     methods: {
      touchstart (e) {
       if (this.temporaryData.tracking) {
        return
       }
       //    touch
       if (e.type === 'touchstart') {
        if (e.touches.length > 1) {
         this.temporaryData.tracking = false
         return
        } else {
         //       
         this.basicdata.start.t = new Date().getTime()
         this.basicdata.start.x = e.targetTouches[0].clientX
         this.basicdata.start.y = e.targetTouches[0].clientY
         this.basicdata.end.x = e.targetTouches[0].clientX
         this.basicdata.end.y = e.targetTouches[0].clientY
        }
       // pc  
       } else {
        this.basicdata.start.t = new Date().getTime()
        this.basicdata.start.x = e.clientX
        this.basicdata.start.y = e.clientY
        this.basicdata.end.x = e.clientX
        this.basicdata.end.y = e.clientY
       }
       this.temporaryData.tracking = true
       this.temporaryData.animation = false
      },
      touchmove (e) {
       //       
       if (this.temporaryData.tracking && !this.temporaryData.animation) {
        if (e.type === 'touchmove') {
         this.basicdata.end.x = e.targetTouches[0].clientX
         this.basicdata.end.y = e.targetTouches[0].clientY
        } else {
         this.basicdata.end.x = e.clientX
         this.basicdata.end.y = e.clientY
        }
        //      
        this.temporaryData.poswidth = this.basicdata.end.x - this.basicdata.start.x
        this.temporaryData.posheight = this.basicdata.end.y - this.basicdata.start.y
       }
      },
      touchend (e) {
       this.temporaryData.tracking = false
       this.temporaryData.animation = true
       //     ,    
       //           100       
       if (Math.abs(this.temporaryData.poswidth) >= 100) {
        //          x 200     
        let ratio = Math.abs(this.temporaryData.posheight / this.temporaryData.poswidth)
        this.temporaryData.poswidth = this.temporaryData.poswidth >= 0 ? this.temporaryData.poswidth + 200 : this.temporaryData.poswidth - 200
        this.temporaryData.posheight = this.temporaryData.posheight >= 0 ? Math.abs(this.temporaryData.poswidth * ratio) : -Math.abs(this.temporaryData.poswidth * ratio)
        this.temporaryData.opacity = 0
       //         
       } else {
        this.temporaryData.poswidth = 0
        this.temporaryData.posheight = 0
       }
      },
      //        
      transform (index) {
       if (index > this.basicdata.currentPage) {
        let style = {}
        let visible = 3
        let perIndex = index - this.basicdata.currentPage
        // visible          
        if (index <= this.basicdata.currentPage + visible - 1) {
         style['opacity'] = '1'
         style['transform'] = 'translate3D(0,0,' + -1 * perIndex * 60 + 'px' + ')'
         style['zIndex'] = visible - index + this.basicdata.currentPage
         style['transitionTimingFunction'] = 'ease'
         style['transitionDuration'] = 300 + 'ms'
        } else {
         style['zIndex'] = '-1'
         style['transform'] = 'translate3D(0,0,' + -1 * visible * 60 + 'px' + ')'
        }
        return style
       }
      },
      //       
      transformIndex (index) {
       //   3D  
       if (index === this.basicdata.currentPage) {
        let style = {}
        style['transform'] = 'translate3D(' + this.temporaryData.poswidth + 'px' + ',' + this.temporaryData.posheight + 'px' + ',0px)'
        style['opacity'] = this.temporaryData.opacity
        style['zIndex'] = 10
        if (this.temporaryData.animation) {
         style['transitionTimingFunction'] = 'ease'
         style['transitionDuration'] = 300 + 'ms'
        }
        return style
       }
      }
     }
    }
    </script>
    4.미 끄 러 진 후 다음 그림 을 위로 쌓 아 올 립 니 다.
    다시 쌓 는 것 은 구성 요소 의 마지막 기능 이자 가장 중요 하고 복잡 한 기능 이다.우리 코드 에서 stack-item 의 정렬 은 바 인 딩 에 의존 합 니 다.style 의 transformIndex 와 transform 함수 에서 판정 하 는 조건 은 currentPage 입 니 다.currentPage 를 바 꾸 고+1 을 하면 다시 쌓 을 수 있 습 니까?
    답 은 그리 간단 하지 않 습 니 다.우 리 는 애니메이션 효과 이기 때문에 300 ms 의 시간 을 진행 할 것 입 니 다.한편,currentPage 변화 로 인 한 재 배열 은 바로 변화 하고 애니메이션 의 진행 을 중단 할 것 입 니 다.따라서 transform 함수 의 정렬 조건 을 수정 한 다음 currentPage 를 바 꿔 야 합 니 다.
    구체 적 실현
  • transform 함수 정렬 조건 수정
  • currentPage+1
  • onTransition End 이 벤트 를 추가 하여 슬라이드 가 끝 난 후 stack 목록 에 다시 설치 합 니 다.
  • 코드 는 다음 과 같 습 니 다:
    
    <template>
      <ul class="stack">
       <li class="stack-item" v-for="(item, index) in pages"
       :style="[transformIndex(index),transform(index)]"
       @touchmove.stop.capture="touchmove"
       @touchstart.stop.capture="touchstart"
       @touchend.stop.capture="touchend"
       @mousedown.stop.capture="touchstart"
       @mouseup.stop.capture="touchend"
       @mousemove.stop.capture="touchmove"
       @webkit-transition-end="onTransitionEnd"
       @transitionend="onTransitionEnd"
       >
        <img :src="item.src">
       </li>
      </ul>
    </template>
    <script>
    export default {
     props: {
      // pages           
      pages: {
       type: Array,
       default: []
      }
     },
     data () {
      return {
       // basicdata          
       basicdata: {
        start: {}, //       
        end: {}, //       
        currentPage: 0 //        
       },
       // temporaryData          
       temporaryData: {
        poswidth: '', //     
        posheight: '', //     
        lastPosWidth: '', //         
        lastPosHeight: '', //         
        tracking: false, //      ,      ,    
        animation: false, //           ,    
        opacity: 1, //        
        swipe: false // onTransition    
       }
      }
     },
     methods: {
      touchstart (e) {
       if (this.temporaryData.tracking) {
        return
       }
       //    touch
       if (e.type === 'touchstart') {
        if (e.touches.length > 1) {
         this.temporaryData.tracking = false
         return
        } else {
         //       
         this.basicdata.start.t = new Date().getTime()
         this.basicdata.start.x = e.targetTouches[0].clientX
         this.basicdata.start.y = e.targetTouches[0].clientY
         this.basicdata.end.x = e.targetTouches[0].clientX
         this.basicdata.end.y = e.targetTouches[0].clientY
        }
       // pc  
       } else {
        this.basicdata.start.t = new Date().getTime()
        this.basicdata.start.x = e.clientX
        this.basicdata.start.y = e.clientY
        this.basicdata.end.x = e.clientX
        this.basicdata.end.y = e.clientY
       }
       this.temporaryData.tracking = true
       this.temporaryData.animation = false
      },
      touchmove (e) {
       //       
       if (this.temporaryData.tracking && !this.temporaryData.animation) {
        if (e.type === 'touchmove') {
         this.basicdata.end.x = e.targetTouches[0].clientX
         this.basicdata.end.y = e.targetTouches[0].clientY
        } else {
         this.basicdata.end.x = e.clientX
         this.basicdata.end.y = e.clientY
        }
        //      
        this.temporaryData.poswidth = this.basicdata.end.x - this.basicdata.start.x
        this.temporaryData.posheight = this.basicdata.end.y - this.basicdata.start.y
       }
      },
      touchend (e) {
       this.temporaryData.tracking = false
       this.temporaryData.animation = true
       //     ,    
       //           100       
       if (Math.abs(this.temporaryData.poswidth) >= 100) {
        //          x 200     
        let ratio = Math.abs(this.temporaryData.posheight / this.temporaryData.poswidth)
        this.temporaryData.poswidth = this.temporaryData.poswidth >= 0 ? this.temporaryData.poswidth + 200 : this.temporaryData.poswidth - 200
        this.temporaryData.posheight = this.temporaryData.posheight >= 0 ? Math.abs(this.temporaryData.poswidth * ratio) : -Math.abs(this.temporaryData.poswidth * ratio)
        this.temporaryData.opacity = 0
        this.temporaryData.swipe = true
        //         
        this.temporaryData.lastPosWidth = this.temporaryData.poswidth
        this.temporaryData.lastPosHeight = this.temporaryData.posheight
        // currentPage+1       
        this.basicdata.currentPage += 1
        // currentPage  ,  dom    ,        
        this.$nextTick(() => {
         this.temporaryData.poswidth = 0
         this.temporaryData.posheight = 0
         this.temporaryData.opacity = 1
        })
       //         
       } else {
        this.temporaryData.poswidth = 0
        this.temporaryData.posheight = 0
        this.temporaryData.swipe = false
       }
      },
      onTransitionEnd (index) {
       // dom     ,                  
       if (this.temporaryData.swipe && index === this.basicdata.currentPage - 1) {
        this.temporaryData.animation = true
        this.temporaryData.lastPosWidth = 0
        this.temporaryData.lastPosHeight = 0
        this.temporaryData.swipe = false
       }
      },
      //        
      transform (index) {
       if (index > this.basicdata.currentPage) {
        let style = {}
        let visible = 3
        let perIndex = index - this.basicdata.currentPage
        // visible          
        if (index <= this.basicdata.currentPage + visible - 1) {
         style['opacity'] = '1'
         style['transform'] = 'translate3D(0,0,' + -1 * perIndex * 60 + 'px' + ')'
         style['zIndex'] = visible - index + this.basicdata.currentPage
         style['transitionTimingFunction'] = 'ease'
         style['transitionDuration'] = 300 + 'ms'
        } else {
         style['zIndex'] = '-1'
         style['transform'] = 'translate3D(0,0,' + -1 * visible * 60 + 'px' + ')'
        }
        return style
       //         
       } else if (index === this.basicdata.currentPage - 1) {
        let style = {}
        //       
        style['transform'] = 'translate3D(' + this.temporaryData.lastPosWidth + 'px' + ',' + this.temporaryData.lastPosHeight + 'px' + ',0px)'
        style['opacity'] = '0'
        style['zIndex'] = '-1'
        style['transitionTimingFunction'] = 'ease'
        style['transitionDuration'] = 300 + 'ms'
        return style
       }
      },
      //       
      transformIndex (index) {
       //   3D  
       if (index === this.basicdata.currentPage) {
        let style = {}
        style['transform'] = 'translate3D(' + this.temporaryData.poswidth + 'px' + ',' + this.temporaryData.posheight + 'px' + ',0px)'
        style['opacity'] = this.temporaryData.opacity
        style['zIndex'] = 10
        if (this.temporaryData.animation) {
         style['transitionTimingFunction'] = 'ease'
         style['transitionDuration'] = 300 + 'ms'
        }
        return style
       }
      }
     }
    }
    </script>
    ok~위의 네 단 계 를 완 성 했 습 니 다.중첩 구성 요소 의 기본 기능 이 이미 실현 되 었 습 니 다.어서 효 과 를 보 세 요.

    중첩 미끄럼 효과 가 이미 나 왔 지만 탐색 은 체험 에 있어 서 접촉 각도 의 오프셋 을 증가 하고 미끄럼 면적 비례 를 판정 했다.
    각도 오프셋 의 원 리 는 사용자 가 touch 를 할 때마다 사용자 의 터치 위 치 를 기록 하고 가장 큰 오프셋 각 도 를 계산 하 며 미끄럼 에 변위 가 나타 날 때 선형 증가 각 도 를 최대 의 오프셋 각도 로 하 는 것 이다.
    stack 에서 구체 적 으로 해 야 할 일 은:
    touch move 에서 필요 한 각도 와 방향 을 계산 합 니 다.
    touch end 및 onTransition End 에서 각 도 를 0 으로 합 니 다.
    미끄럼 면적 비례 를 판정 하고 주로 편 이 량 을 통 해 편 이 면적 을 계산 하여 면적 비례 를 얻어 판단 을 완성 한다
    완전한 코드 와 demo 는github에서 원본 코드 를 볼 수 있 습 니 다.여 기 는 붙 이지 않 습 니 다.
    이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

    좋은 웹페이지 즐겨찾기