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 를 통 해 목표 의 위 치 를 바 꾸 는 것 이다.따라서 우리 가 실현 해 야 할 절 차 는 다음 과 같다.
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 를 바 꿔 야 합 니 다.
구체 적 실현
<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에서 원본 코드 를 볼 수 있 습 니 다.여 기 는 붙 이지 않 습 니 다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Fastapi websocket 및 vue 3(Composition API)1부: FastAPI virtualenv 만들기(선택 사항) FastAPI 및 필요한 모든 것을 다음과 같이 설치하십시오. 생성main.py 파일 및 실행 - 브라우저에서 이 링크 열기http://127.0.0.1:...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.