Vue 上下滑动抽屉菜单实现
上下拉-抽屉菜单实现在开发中一般我们都使用左右侧换菜单,而上下抽屉菜单却很少见,今天我们实现一个简单的上下拉抽屉菜单。首先我们先直观的看下上下拉抽屉菜单的实现效果:在学习抽屉菜单前我们先学习标签的touch事件。一、事件:1、标签所对应touch事件:touchstart事件:当手指触摸屏幕时候触发,即使已经有一个手指放在屏幕上也会触发。touchmove事件:当手指在屏幕上滑动的时候连续地触发.
·
上下拉-抽屉菜单实现
在开发中一般我们都使用左右侧换菜单,而上下抽屉菜单却很少见,今天我们实现一个简单的上下拉抽屉菜单。
首先我们先直观的看下上下拉抽屉菜单的实现效果:
在学习抽屉菜单前我们先学习标签的touch事件。
一、事件:
1、标签所对应touch事件:
- touchstart事件:当手指触摸屏幕时候触发,即使已经有一个手指放在屏幕上也会触发。
- touchmove事件:当手指在屏幕上滑动的时候连续地触发。在这个事件发生期间,调用preventDefault()事件可以阻止滚动。
- touchend事件:当手指从屏幕上离开的时候触发。
- touchcancel事件:当系统停止跟踪触摸的时候触发。关于这个事件的确切出发时间,文档中并没有具体说明,咱们只能去猜测了。
2、而每种touch的event事件都包含如下属性:
- clientX:触摸目标在视口中的x坐标。
- clientY:触摸目标在视口中的y坐标。
- identifier:标识触摸的唯一ID。
- pageX:触摸目标在页面中的x坐标。
- pageY:触摸目标在页面中的y坐标。
- screenX:触摸目标在屏幕中的x坐标。
- screenY:触摸目标在屏幕中的y坐标。
- target:触目的DOM节点目标。
3、事件监听处理
a) 声明标签对,并设置ref属性
<div ref="drawer">
...
</div>
b) 通过ref获取当前div标签,对div标签进行监听
this.$refs.drawer.addEventListener('touchstart', (event) => {
console.log('====touchstart======', event)
})
this.$refs.drawer.addEventListener('touchmove', (event) => {
console.log('====touchmove======', event)
})
this.$refs.drawer.addEventListener('touchend', (event) => {
console.log('====touchend======', event)
})
this.$refs.drawer.addEventListener('touchcancel', (event) => {
console.log('=====touchcancel=====', event)
})
学习完事件后,接下来就实现抽屉效果。首先我们将抽屉布局固定到底部,接下来我们只需要改变抽屉的高度即可实现打开抽屉和关闭抽屉效果。
4、实现步骤:
- touchstart事件触发时记录用户触摸的位置
- touchmove事件触发时计算用户移动的距离
- touchend事件触发时根据用户移动的偏移量的正负值判断方向,并设置抽屉到打开回或关闭状态
a) touchstart事件触发处理:
this.$refs.drawer.addEventListener('touchstart', (event) => {
//记录点击坐标
this.startPos.y = event.targetTouches[0].pageY
this.defaultHeight = this.currentHeight
})
b) touchmove事件计算处理
this.$refs.drawer.addEventListener('touchmove', (event) => {
//计算移动距离
if (event.targetTouches.length > 1) {
return
}
let touch = event.targetTouches[0]
let dy = ((this.startPos.y - touch.pageY) * 100) / 667
if (this.isToped && !this.isScollToTop && dy < 0) {
this.endPos.y = touch.pageY
this.currentHeight = Math.max(
this.window.startHeight,
this.defaultHeight + dy
)
console.log(this.currentHeight)
event.preventDefault()
} else if (this.isToped && dy > 0 && this.isScollToTop) {
//向上
event.preventDefault()
this.endPos.y = touch.pageY
this.currentHeight = Math.min(
this.window.endHeight,
this.defaultHeight + dy
)
console.log(this.currentHeight)
}
})
c) touchend事件触发时处理:
this.$refs.drawer.addEventListener('touchend', (event) => {
console.log('====touchend======', event)
// 阈值 = 差值的20%
const threshold =
Math.abs(this.defaultHeight - this.currentHeight) * 0.2
if (!this.isToped || threshold === 0) {
return
}
const scrollDy = Math.abs(this.endPos.y - this.startPos.y)
const isDirectorTop = this.endPos.y - this.startPos.y < 0
const isToTop =
(isDirectorTop && scrollDy > threshold) ||
(!isDirectorTop && scrollDy < threshold)
// 60Hz, 16.6ms
let id = setInterval(() => {
if (isToTop) {
this.isScollToTop = false
this.currentHeight = this.window.endHeight
clearInterval(id)
} else {
this.currentHeight = this.window.startHeight
this.isScollToTop = true
clearInterval(id)
}
}, 10)
})
d)用户点击头部打开或关闭抽屉事件处理:
openDrawable() {
// 60Hz, 16.6ms
let id = setInterval(() => {
if (this.isScollToTop) {
this.isScollToTop = false
this.currentHeight = this.window.endHeight
clearInterval(id)
} else {
this.currentHeight = this.window.startHeight
this.isScollToTop = true
clearInterval(id)
}
}, 10)
},
最后附上完整抽屉组件分装代码:
<template>
<div class="VerticalDrawable" :style="heightStyle" ref="drawer">
<div class="title" @click="openDrawable">{{ title }}</div>
<div class="listview" @scroll.passive="onListScrolled($event)">
<div v-for="(item, index) in list" :key="index">
<div class="listItem">{{ item }}</div>
</div>
</div>
</div>
</template>
<script>
export default {
components: {},
props: {
title: {
type: String,
default: '图书馆借阅记录',
},
list: {
type: Array,
default: () => {
return []
},
},
},
data() {
return {
isToped: true, //判断列表的第一个元素是否位于顶部
isScollToTop: true, //true 向顶部滑动
defaultHeight: 46, //记录默认高度 %
currentHeight: 46, //记录当前窗体高度%
window: {
startHeight: 46,
endHeight: 82,
},
startPos: {
y: 0,
},
endPos: {
y: 0,
},
}
},
computed: {
heightStyle() {
return {
height: this.currentHeight + '%',
}
},
},
created() {},
mounted() {
this.handleTounchListener()
},
methods: {
openDrawable() {
// 60Hz, 16.6ms
let id = setInterval(() => {
if (this.isScollToTop) {
this.isScollToTop = false
this.currentHeight = this.window.endHeight
clearInterval(id)
} else {
this.currentHeight = this.window.startHeight
this.isScollToTop = true
clearInterval(id)
}
}, 10)
},
onListScrolled(event) {
let top = event.target.scrollTop
this.isToped = top === 0
},
handleTounchListener() {
this.$refs.drawer.addEventListener('touchstart', (event) => {
//记录点击坐标
this.startPos.y = event.targetTouches[0].pageY
this.defaultHeight = this.currentHeight
})
this.$refs.drawer.addEventListener('touchmove', (event) => {
//计算移动距离
if (event.targetTouches.length > 1) {
return
}
let touch = event.targetTouches[0]
let dy = ((this.startPos.y - touch.pageY) * 100) / 667
if (this.isToped && !this.isScollToTop && dy < 0) {
this.endPos.y = touch.pageY
this.currentHeight = Math.max(
this.window.startHeight,
this.defaultHeight + dy
)
console.log(this.currentHeight)
event.preventDefault()
} else if (this.isToped && dy > 0 && this.isScollToTop) {
//向上
event.preventDefault()
this.endPos.y = touch.pageY
this.currentHeight = Math.min(
this.window.endHeight,
this.defaultHeight + dy
)
console.log(this.currentHeight)
}
})
this.$refs.drawer.addEventListener('touchend', (event) => {
console.log('====touchend======', event)
// 阈值 = 差值的20%
const threshold =
Math.abs(this.defaultHeight - this.currentHeight) * 0.2
if (!this.isToped || threshold === 0) {
return
}
const scrollDy = Math.abs(this.endPos.y - this.startPos.y)
const isDirectorTop = this.endPos.y - this.startPos.y < 0
const isToTop =
(isDirectorTop && scrollDy > threshold) ||
(!isDirectorTop && scrollDy < threshold)
// 60Hz, 16.6ms
let id = setInterval(() => {
if (isToTop) {
this.isScollToTop = false
this.currentHeight = this.window.endHeight
clearInterval(id)
} else {
this.currentHeight = this.window.startHeight
this.isScollToTop = true
clearInterval(id)
}
}, 10)
})
this.$refs.drawer.addEventListener('touchcancel', (event) => {
console.log('=====touchcancel=====', event)
})
},
onClickMore() {
this.$emit('onClickMore')
},
btnClick(item) {
debugger
this.$emit('returnBookClick', item)
},
cellClick(item) {
debugger
this.$emit('bookCellClick', item)
},
},
}
</script>
<style lang="scss" scoped>
.VerticalDrawable {
position: fixed;
left: 0px;
right: 0px;
bottom: 0px;
overflow-y: hidden;
background: #ffffff;
.title {
line-height: 50px;
text-align: center;
background: chocolate;
}
.listview {
display: flex;
height: 100%;
flex-direction: column;
overflow-y: auto;
.listItem {
line-height: 50px;
text-align: center;
border-bottom: 1px solid #999999;
margin-bottom: -1px;
}
}
}
</style>
在父组件中使用抽屉组件如下:
<template>
<div class="BookHome">
<div class="toplayout">
<img :src="srcUrl" alt="">
<div>顶部样式布局</div>
</div>
<div class="drawer">
<VerticalDrawer
:list='list' />
</div>
</div>
</template>
<script>
import VerticalDrawer from "./VerticalDrawer";
export default {
components: {
VerticalDrawer,
},
data() {
return {
list: [],
srcUrl:require('../assets/logo.png'),
}
},
created () {
for(var i=0;i<100;i++){
this.list[i] = '第'+i+'条数据';
}
},
}
</script>
<style lang="scss" scoped>
.BookHome{
display: flex;
height: 100%;
flex-direction: column;
.toplayout{
height: 50%;
text-align: center;
border: 1px solid chocolate;
}
}
</style>
更多推荐
已为社区贡献1条内容
所有评论(0)