工作中遇到的,简单总结一下吧。
需求背景
老板一看设计稿,就和我说,要做得和百度地图app里的一样丝滑~
具体实现
代码比较简单,直接贴上就好了
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<title>H5仿百度,腾讯地图拖拽面板</title>
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html, body, #app {
height: 100%;
position: relative;
overflow: hidden;
}
.container {
width: 100%;
height: 100%;
background: #ccc;
position: absolute;
top: 100%;
}
.container.moving {
transition: all .2s;
transition-timing-function: ease-out;
}
.header {
height: 100px;
line-height: 100px;
text-align: center;
background: darkgoldenrod;
}
.box {
height: calc(100% - 100px);
overflow-y: hidden;
}
.box.scroll {
overflow-y: scroll;
}
.box .content {
height: 50px;
text-align: center;
}
</style>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.js"></script>
</head>
<body>
<div id="app">
<div class="container" :class="{'moving': !moving}" :style="{height: `${maxHeight}px`, transform: `translateY(${-translate}px)`}" @touchmove.stop="touchmoveHandle" @touchstart.stop="touchstartHandle" @touchend.stop="touchendHandle">
<div class="header">header</div>
<div class="box" ref="box" @touchmove="touchmoveBoxHandle" @touchstart="touchstartBoxHandle" @touchend="touchendBoxHandle" :class="{'scroll': isScroll}">
<div class="content" v-for="item in 20">content{{item}}</div>
</div>
</div>
</div>
<script>
/*
* 需要展示效果:
* 1. 根据手指移动的距离实时移动
* 2. 划动结束时位移到某个高度
* 3. 仅在最高高度时可以内部滚动
*
* 实现思路:
* 1. 先设定3个高度,最大高度=屏幕高度,最小高度,中间高度
* 2. 从结构上将面板设置为最大高度,定位到屏幕的最下方,通过改变translateY来进行展示
* 3. 划动结束需要位移到哪个高度(上移或者下移)可以有多种方式
* 1:根据初次划动的方向来确定
* 2:根据划动结束时的位置靠近哪个高度来确定
* 3:根据结束划动前的方向来确定
* 4:(高德和腾讯)从使用体验感觉上来说:好像是区分了短划动和长划动,短划时应该时第一种,长划时应用了第二种
* 4. ios回弹问题
*/
const App = new Vue({
el: '#app',
data: {
moving: false, // 判断是否进行了拖拽,防止点击时误触发touchend
maxHeight: 0, // 最大高度
middleHeight: 0, // 中间高度
minHeight: 100, // 最小高度
translate: 0, // 位移高度
isScroll: false // 内部滚动
},
methods: {
touchmoveHandle(event) {
this.moving = true;
let currentY = event.touches[0].pageY;
// 记录滑动值
const upAndDown = this.prevY - currentY;
this.translate = this.translate + upAndDown;
// 最大最小边界判断
if (this.translate > this.maxHeight) {
this.translate = this.maxHeight
}
if (this.translate < this.minHeight) {
this.translate = this.minHeight
}
this.prevY = currentY;
// 在初次滑动时判断结束的方向
if (this.direction_flag) {
this.direction_flag = false;
this.up = !((currentY - this.startY) > 0)
}
},
touchstartHandle(event) {
// 记录初始值
this.startY = event.touches[0].pageY;
this.prevY = this.startY;
this.direction_flag = true;
},
touchendHandle(event) {
if (!this.moving) return; // 防止点击触发
this.moving = false;
// 根据方向判断最终展示的位移高度
if (this.up) {
if (this.translate < this.middleHeight) {
this.translate = this.middleHeight
this.isScroll = false
} else {
this.translate = this.maxHeight
this.isScroll = true
}
} else {
if (this.translate < this.middleHeight) {
this.translate = this.minHeight
this.isScroll = false
} else {
this.translate = this.middleHeight
this.isScroll = false
}
}
this.direction_flag = true
},
touchstartBoxHandle(e) {
// ios回弹处理
if (this.isIos && this.isScroll) {
// 根据滚动条位置判断是否允许滚动
this.allowUp = this.box.scrollTop > 0;
this.allowDown = (this.box.scrollTop < this.box.scrollHeight - this.box.clientHeight);
this.slideBeginY = e.pageY; // e.targetTouches[0].pageY也可以
}
if (this.box.scrollTop !== 0) {
this.stop = true;
e.stopPropagation();
} else {
this.stop = false;
}
},
touchmoveBoxHandle(e) {
if (this.isIos && this.isScroll) {
const up = (e.pageY > this.slideBeginY);
const down = (e.pageY < this.slideBeginY);
this.slideBeginY = e.pageY;
if ((up && this.allowUp) || (down && this.allowDown)) {
e.stopPropagation();
} else {
e.preventDefault();
}
}
if (this.stop) e.stopPropagation()
},
touchendBoxHandle(e) {
if (this.stop) e.stopPropagation()
}
},
mounted() {
this.isIos = window.navigator.userAgent.indexOf('Mac OS X') > -1;
// 记录屏幕高度
this.maxHeight = window.innerHeight
this.middleHeight = this.maxHeight / 2
this.translate = this.middleHeight
this.box = this.$refs['box'];
}
})
</script>
</body>
</html>