前言
最近在做专科毕设个人资料部分,发现酷安的效果不错,遂开始借鉴。
酷安效果
我的效果
我的思路是把页面分成两块: 1.顶部标题栏区 2.下方主内容区。
顶部标题区分成普通状态,收缩状态,扩张状态 触摸屏幕的时候先判断顶部当前处于哪种状态。只有处于收缩状态时才开启主内容区域滚动。其他状态下主内容是无法滚动的,通过改变头部区域高度挤压下方内容实现下方伪滚动效果。
<template>
<div id="root">
<div id="header" :style="'height:'+navNormalHeight+'px'">
<div>你好</div>
</div>
<div id="content" :style="'height:calc(100% - '+navShrinkHeight+'px)'">
<div v-for="i in 100">{{i}}</div>
</div>
</div>
</template>
先关闭页面整体滚动
document.documentElement.style.overflowY = 'hidden'
监听触摸事件,判断滑动方向。
这里提一句,判断滑动方向网上不少帖子是通过在touchstart手指刚接触屏幕时设个变量,然后滑动时判断手指当前位置是否大于或小于初始触摸位置实现的,这显然是偷懒的做法。刚开始我也这样做,后面被坑了一把:比如初始触摸在y=600的位置,此时向上划到y=400,判断上滑没错,但是不松手下滑到y=500的位置方向判断就出问题了,因为还是在600上方,所以还是判断是上滑动作
设置顶部三个状态的高度
navNormalHeight: 300,//默认300
navShrinkHeight: 65,//缩小状态65
navExpandHeight: 500//扩张状态500
获取顶部和主内容节点
let header = document.getElementById('header')
let content = document.getElementById('content')
设一个变量记录上次手指所在位置 let oldY 设一个变量标识方向 let up 监听touchmove事件,获取第一根手指的触摸对象(这里就不考虑多指触摸了) let touches = ev.touches[0] 手指移动一像素点时给oldY赋值,直接return不进行其他操作。 在移动监听的最后位置再给oldY赋值为当前坐标。
window.addEventListener('touchmove', ev => {
let touches = ev.touches[0]
//第一次移动仅获取坐标,不进行其他操作
if (oldY===undefined){
oldY=touches.clientY
return
}
//比较新旧坐标获得准确方向
up = touches.clientY <= oldY
/*
主要逻辑
*/
//最后记录旧坐标
oldY = touches.clientY
})
处理上划逻辑
1.顶部未完成收缩 先关掉主内容滚动
if (content.style.overflowY === 'scroll') {
content.style.overflowY = 'hidden'
}
计算滑动偏移量 监听touchstart事件,记录刚开始触摸的位置。
window.addEventListener('touchstart', ev => {
let touches = ev.touches[0];
//记录初始位置
startY = touches.clientY
})
上滑偏移量=触摸初始位置-手指当前位置
let offY = startY - touches.clientY
顶部区域高度=顶部默认高度-偏移量
if (header.clientHeight > _this.navShrinkHeight) {
if (content.style.overflowY === 'scroll') {
content.style.overflowY = 'hidden'
}
let number = _this.navNormalHeight - offY
header.style.height = number + 'px'
}
现在已经可以随着手指的上滑,实现顶部区域的收缩了,同时下方区域也在向上填充。
但是如果手指离开后再次触摸接着上次的位置滑动,会发现每次都是从默认的高度开始向上缩小,显然不合理。所以我们再定义一个变量,记录手指离开时的顶部区域高度
监听touchend事件
//初始值为默认高度
let lastHeight=this.navNormalHeight
window.addEventListener('touchend', ev => {
lastHeight = header.clientHeigh
})
手指离开后记录顶部高度,下次动态设置高度的时候减去默认高度与上次的高度的偏差即可
let number = _this.navNormalHeight - offY - (_this.navNormalHeight - lastHeight);
header.style.height = number + 'px'
此时手指离开后重新上滑的高度问题解决。 又发现一个问题: 我们修改顶部高度的条件是顶部高度大于缩小状态高度,到缩小状态也停止缩小了,但是高度不对啊,设好的65不灵了 我猜测是滑动延时,执行延时导致的偏差(移动端点击事件延迟300毫秒)。只需要在修改高度的语句下调用下修正函数即可。
function formatY(){
//避免过度缩小
if (_this.navHeight <= _this.navShrinkHeight) {
_this.navHeight = _this.navShrinkHeight
}
//避免过度扩张
if (_this.navHeight >= _this.navExpandHeight) {
_this.navHeight = _this.navExpandHeight
}
}
修改完高度如果有偏差就再调整到设计的范围内即可。 至此顶部普通状态到收缩状态的过程写完了。 2.顶部完成收缩 此时就不用对顶部高度进行修改了。 打开下方主内容滑动
content.style.overflowY = 'scroll'
此时下方可以滑动,但是从未收缩状态到收缩状态到主内容打开滑动过程中一直不松手的话主内容动不了,必须抬手重新向上划才可以滑动主内容。缺少连贯性,所以我们手动给主内容设置scrollTop滚动起来。
let offO = offY - (lastHeight - _this.navShrinkHeight)
content.scrollTop = offO
此时有了连贯性,解决下再次触摸滚动位置不对的问题: 对主内容滚动进行监听,停止后记录位置, 由于js没有对滚动结束的监听,只能通过函数防抖,延时200毫秒获取最后一次滚动时主内容的scrollTop
let timeOut
content.addEventListener('scroll', ev => {
if (timeOut !== undefined) {
clearTimeout(timeOut)
}
timeOut = setTimeout(() => {
lastContentY = content.scrollTop
}, 200)
})
修改
let offO = offY - (lastHeight - _this.navShrinkHeight) + lastContentY
content.scrollTop = offO
上滑处理完成,下滑举一反三:
//主内容顶部展现
if (content.scrollTop === 0) {
if (content.style.overflowY === 'scroll') {
content.style.overflowY = 'hidden'
startY = touches.clientY;
lastHeight=_this.navShrinkHeight
}
let offY = touches.clientY - startY
let newHeight = (lastHeight + offY)
header.style.height = newHeight + 'px'
formatHeight()
}
//主内容顶部未出现
else {
let offY = startY - touches.clientY
if (content.style.overflowY === 'hidden') {
content.style.overflowY = 'scroll'
}
let offO = offY - (lastHeight - _this.navShrinkHeight) + lastContentY
content.scrollTop = offO
}
上下滑动已经完成了,处理一下松手回弹: 这里我们用间歇函数每5毫秒减去顶部5高度。
window.addEventListener('touchend', ev => {
lastHeight = header.clientHeight
//高度大于普通高度,触发回弹动画
if (lastHeight>=_this.navNormalHeight){
let interval=setInterval(()=>{
if (header.clientHeight<=_this.navNormalHeight+5){
clearInterval(interval)
}
let height=header.clientHeight-5
header.style.height=height+'px'
if (header.clientHeight<=_this.navNormalHeight+5){
lastHeight=_this.navNormalHeight
}
},5)
}
})
添加阴影遮罩
<div class="header" :style="'height:'+navNormalHeight+'px;'" id="header">
<div id="headerRoot">
<img :src="coverImg" class="coverImg">
<slot name="headerInfo"></slot>
<div id="mask"></div>
</div>
</div>
#mask{
z-index: 8;
height: 100%;
width: 100%;
position: absolute;
pointer-events: none;
}
动态设置透明度
if (header.clientHeight<=_this.navNormalHeight){
let temp = _this.navNormalHeight - header.clientHeight
let val = temp / _this.navNormalHeight
mask.style.background = 'rgba(0, 0, 0, '+val+')'
}
完整代码
<template>
<div class="root">
<div class="header" :style="'height:'+navNormalHeight+'px;'" id="header">
<div id="headerRoot">
<img :src="coverImg" class="coverImg">
<div id="imgMask"></div>
<slot name="headerInfo"></slot>
<div id="mask"></div>
</div>
</div>
<div class="content" id="content">
<slot name="content"></slot>
</div>
</div>
</template>
<script>
export default {
name: "Temp",
props: {
navNormalHeight: 0,
navShrinkHeight: 0,
navExpandHeight: 0,
coverImg: ''
},
mounted() {
let _this = this;
let header = document.getElementById('header')
let content = document.getElementById('content')
let mask = document.getElementById('mask')
document.documentElement.style.overflowY = 'hidden'
let startY, lastContentY = 0
let oldY
let up, lastHeight = _this.navNormalHeight
let beTouch=false
function start(ev) {
let touches = ev.touches[0];
startY = touches.clientY
beTouch=true
}
function move(ev) {
let touches = ev.touches[0]
//第一次移动仅获取坐标,不进行其他操作
if (oldY === undefined) {
oldY = touches.clientY
return
}
up = touches.clientY <= oldY
// console.log(up);
//上滑
if (up) {
let offY = startY - touches.clientY
if (header.clientHeight > _this.navShrinkHeight) {
if (content.style.overflowY === 'scroll') {
content.style.overflowY = 'hidden'
}
let number = _this.navNormalHeight - offY - (_this.navNormalHeight - lastHeight);
header.style.height = number + 'px'
formatHeight()
}
//顶部收缩完成
else {
// console.log(content.style.overflowY)
//打开主内容滑动(下方滑动)
content.style.overflowY = 'scroll'
let offO = offY - (lastHeight - _this.navShrinkHeight) + lastContentY
content.scrollTop = offO
}
}
//下滑
else {
//主内容顶部展现
if (content.scrollTop === 0) {
if (content.style.overflowY === 'scroll') {
content.style.overflowY = 'hidden'
startY = touches.clientY;
lastHeight = _this.navShrinkHeight
}
let offY = touches.clientY - startY
let newHeight = (lastHeight + offY)
header.style.height = newHeight + 'px'
formatHeight()
}
//主内容顶部未出现
else {
let offY = startY - touches.clientY
if (content.style.overflowY === 'hidden') {
content.style.overflowY = 'scroll'
}
let offO = offY - (lastHeight - _this.navShrinkHeight) + lastContentY
content.scrollTop = offO
}
}
if (header.clientHeight <= _this.navNormalHeight) {
let temp = _this.navNormalHeight - header.clientHeight
let val = (temp / _this.navNormalHeight) - 0.2
mask.style.background = 'rgba(0, 0, 0, ' + val + ')'
}
if (header.clientHeight <= _this.navExpandHeight) {
_this.$emit('navHide', header.clientHeight)
}
//电脑端模拟手机滑动到屏幕外修正
if (touches.clientY < document.documentElement.clientHeight
&& touches.clientX < 0 && touches.clientX > document.documentElement.clientWidth) {
oldY = touches.clientY
}
oldY = touches.clientY
}
function end(ev) {
beTouch=false
lastHeight = header.clientHeight
//高度大于普通高度,触发回弹动画
if (lastHeight >= _this.navNormalHeight) {
let interval = setInterval(() => {
if (header.clientHeight <= _this.navNormalHeight + 5) {
clearInterval(interval)
}
let height = header.clientHeight - 5
header.style.height = height + 'px'
if (header.clientHeight <= _this.navNormalHeight + 5) {
lastHeight = _this.navNormalHeight
}
}, 5)
}
}
//函数防抖
let timeOut
function beScroll(ev) {
if (timeOut !== undefined) {
clearTimeout(timeOut)
}
if (beTouch){
return
}
timeOut = setTimeout(() => {
lastContentY = content.scrollTop
}, 1)
}
function formatHeight() {
if (header.clientHeight < _this.navShrinkHeight) {
header.style.height = _this.navShrinkHeight + 'px'
}
if (header.clientHeight >= _this.navExpandHeight) {
header.style.height = _this.navExpandHeight + 'px'
}
}
window.addEventListener('touchstart', start)
window.addEventListener('touchmove', move)
window.addEventListener('touchend', end)
content.addEventListener('scroll', beScroll)
this.$on('hook:beforeDestroy', () => {
console.log('销毁事件')
window.removeEventListener('touchstart',start)
window.removeEventListener('touchmove',move)
window.removeEventListener('touchend',end)
content.removeEventListener('scroll',beScroll)
document.documentElement.style.overflowY = 'auto'
})
}
}
</script>
<style lang="less" scoped>
.coverImg {
width: 100%;
height: 100%;
object-fit: cover;
position: absolute;
}
.root {
height: 100%;
width: 100%;
position: fixed;
}
.header {
width: 100%;
overflow: hidden;
}
.content {
height: calc(100% - 65px);
width: 100%;
background-color: @bg-color;
overflow-y: hidden;
}
#mask {
z-index: 8;
height: 100%;
width: 100%;
position: absolute;
pointer-events: none;
}
#imgMask {
height: 100%;
width: 100%;
position: absolute;
background-color: rgba(0, 0, 0, 0.45);
pointer-events: none;
}
#headerRoot {
position: relative;
height: 100%;
width: 100%;
}
</style>
正常使用,效果还不错。
|