最近写项目要用到类似抖音的我的页面,在顶部用户执行下拉操作时顶部背景图会放大并高度增加的效果,使页面感觉更加的丝滑。先上效果:
文章末尾有完整的代码,着急用的可以直接跳转的文章最后。
2022.5.24 因为性能问题,又在最下边重新优化的最新代码,但是思路还是不变的。
上代码之前先了解几个事件:
@touchstart 当用户手指按下时
@touchmove 当用户手指移动过程中
@touchend 当用户手指抬起时
这几个事件会记录用户手指在屏幕和页面上的位置信息,还有一个uniapp的生命周期事件onPageScroll()监听页面滚动。
!!!这几个事件一定要绑定在最外层的标签上,监听整个页面!!!
这个功能的实现思路大概是:
第一步:先监听到当前位置是不是页面视口的顶部onPageScroll(),再监听用户手指的动作。
第二步:当视口位于页面的顶部并且用户做了下拉动作,则动态的修改顶部背景图的高度,宽度(放大效果)。
第三步:当用户手指抬起时@touchend,将图片恢复到原来到宽高。
按照我们的思路一步一步用代码实现:
第一步:先监听到当前位置是不是页面视口的顶部onPageScroll(),再监听用户手指的动作。
<view class="my" @touchmove="move" @touchend="end">
//绑定监听事件,当用户手指移动时和用户手指抬起时
</view>
<script>
export default {
data() {
return {
// 用户操作
clientData: {
// 用户手指Y轴
clientMoveY: '', //按下
clientEndY: '', //抬起
// 页面Y轴
pageY: ''
}
}
},
onPageScroll(e) {
//获取当前页面滚动高度
this.clientData.pageY = e.scrollTop;
},
methods: {
// 手指移动
move(e) {
},
// 手指抬起
end(e) {
},
}
}
</script>
第二步:当视口位于页面的顶部并且用户做了下拉动作,则动态的修改顶部背景图的高度,宽度。(我这里图片给图片放大用的是transform:scale())
onPageScroll(e) {
//获取当前页面滚动高度
this.clientData.pageY = e.scrollTop;
},
methods: {
// 手指移动
move(e) {
// 先判断用户是否到达了顶部
if (this.clientData.pageY < 1) {
// 本次手指移动的位置和上次移动的位置对比 <1 证明用户在下拉
if (this.clientData.clientMoveY - e.changedTouches[0].clientY < 1) {
// 拿到下拉的距离
let distance = this.clientData.clientMoveY - e.changedTouches[0].clientY;
// 最大下拉到800rpx ,放大1.8倍大小
if (this.sheight < 800 && this.imgWidth < 1.8) {
this.sheight -= distance;
this.imgWidth -= distance / 1000;
} else {
//超过800或者1.8倍就return
return;
}
}
// 上边的代码执行结束之后再把本次手指的位置赋值给data中,用来下一次对比
this.clientData.clientMoveY = e.changedTouches[0].clientY;
}
},
// 手指抬起
end(e) {
// this.clientData.clientEndY = e.changedTouches[0].clientY;
// 当图片的高度大于400的时候手指抬起再调用函数
if (this.sheight > 400) {
// 执行动画
this.isPlay = true;
// 动画结束后把顶部图片的高度和放大比例该会原来的值
setTimeout(() => {
this.sheight = 400;
this.imgWidth = 1;
// 并把动画的class名取消掉
this.isPlay = false;
}, 504);
}
},
}
第三步:当用户手指抬起时@touchend,将图片恢复到原来到宽高。
这里我们需要先定义一个动画,当用户手指抬起是执行这个动画:
// 一个动画
.isPlay {
animation: big 0.5s 1 alternate ease-in-out forwards;
}
@keyframes big {
100% {
transform: scale(1);//图片放大比例恢复为1
height: 400rpx;//高度恢复为原来的高度
}
}
这个isPlay的class名需要动态的绑定给image,执行动画时绑定上,执行动画,当动画执行完,接触,等待下一次触发事件。
end(e) {
// 当图片的高度大于400的时候手指抬起再调用函数
if (this.sheight > 400) {
// 执行动画
this.isPlay = true;
// 动画结束后把顶部图片的高度和放大比例该会原来的值
setTimeout(() => {
this.sheight = 400;
this.imgWidth = 1;
// 并把动画的class名取消掉
this.isPlay = false;
}, 504);//我这里动画定义的是0.5s结束
}
},
大概就是这么个思路,动态绑定和一些小的地方就一一写出来了。
这个效果的实现肯定有很多种,这只是其中的一种,比如图片放大效果,也可以直接更改图片的宽度来实现放大。
完整代码:
<template>
<view class="my" @touchend="end" @touchmove="move">
<!-- 顶部背景图 -->
<view :class="{'banner':true,'isPlay':isPlay}" :style="{ height: sheight + 'rpx' }">
<view class="">
<image :class="{'isPlay':isPlay}" :style="{ height: sheight + 'rpx',transform:`scale(${imgWidth})`}" src="https://p4.music.126.net/nILBk4DaE3yV__25uq-5GQ==/18641120139241412.jpg?param=640y300" mode=""></image>
</view>
</view>
</view>
</template>
<script>
export default {
components: {
},
data() {
return {
sheight: 400,//高度
imgWidth:1,//放大比例
isPlay:false,//是否播放动画
// 用户滑动
clientData: {
// 用户手指Y轴
clientMoveY: '', //按下
clientEndY: '', //抬起
// 页面Y轴
pageY: ''
},
};
},
onLoad() {
},
onPageScroll(e) {
//获取当前页面滚动高度
this.clientData.pageY = e.scrollTop;
},
methods: {
// 手指移动
move(e) {
// 先判断用户是否到达了顶部
if (this.clientData.pageY < 1) {
// 本次手指移动的位置和上次移动的位置对比 <1 证明用户在下拉
if(this.clientData.clientMoveY - e.changedTouches[0].clientY < 1) {
// 拿到下拉的距离
let distance = this.clientData.clientMoveY - e.changedTouches[0].clientY;
// 最大下拉到800rpx ,放大1.8倍大小
if(this.sheight < 800 && this.imgWidth < 1.8){
this.sheight -= distance
this.imgWidth -= distance/500
}else{//超过800或者1.8倍就return 优化性能
return
}
}else{
return
}
// 上边的代码执行结束之后再把本次手指的位置赋值给data中,用来下一次对比
this.clientData.clientMoveY = e.changedTouches[0].clientY;
}
},
// 手指抬起
end(e) {
this.clientData.clientEndY = e.changedTouches[0].clientY;
// 当图片的高度大于400的时候手指抬起再调用函数
if(this.sheight > 400){
// 执行动画
this.isPlay = true
// 动画结束后把顶部图片的高度和放大比例该会原来的值
setTimeout(()=>{
this.sheight=400
this.imgWidth=1
// 并把动画的class名取消掉
this.isPlay = false
},504)
}
}
}
};
</script>
<style lang="scss" scoped>
.banner {
width: 100vw;
overflow: hidden;//防止图片放大,宽度被撑开
background-size: cover;
position: relative;
image {
width: 100%;
max-height: 800rpx;
position: absolute;
bottom: 0;
}
}
// 一个动画
.isPlay{
animation: big 0.5s 1 alternate ease-in-out forwards;
}
@keyframes big{
100% {
transform: scale(1);
height: 400rpx;
}
}
</style>
代码中有任何建议,可以评论下来,一起成长,加油!
感觉有用的小伙伴也可以关注我,后期在开发过程中遇到有趣的功能都会同步到博客上。
<template>
<view
class="my"
@touchstart="coverTouchstart"
@touchmove="coverTouchmove"
@touchend="coverTouchend"
>
<!-- 顶部背景图 -->
<view
class="banner"
:style="{
height: sheight + 'rpx',
transform: coverTransform,
transition: coverTransition,
}"
>
<view class="">
<image
style="height: 100%"
:src="this.form.backgroundPath"
mode="aspectFill"
></image>
</view>
</view>
</view>
</template>
<script>
let startY = 0,//手指按下时屏幕的位置
moveY = 0,//手指当前在屏幕中的位置
pageAtTop = true;//是否到顶
export default {
data() {
return {
coverTransform: "scale(1)", //放大倍数
coverTransition: "0s", //过度时间
moving: false,
form: {},
sheight: 280, //高
};
},
methods: {
coverTouchstart(e) {
if (pageAtTop === false) {
return;
}
this.coverTransition = '.1s ease-out';
startY = e.touches[0].clientY;
},
coverTouchmove(e) {
moveY = e.touches[0].clientY;
let moveDistance = moveY - startY;
if (moveDistance < 0) {
this.moving = false;
return;
}
this.moving = true;
if (moveDistance > 0) {
if (this.sheight > 800) return;
this.sheight = 280 + moveDistance;
this.coverTransform = 'scale(1.08)';
this.coverTransition = '.1s ease-out';
}
},
coverTouchend() {
if (this.moving === false) {
return;
}
this.moving = false;
this.sheight = 280;
this.coverTransform = 'scale(1)';
this.coverTransition = '.2s ease-out';
},
}
}
</script>
<style lang="scss" scoped>
.banner {
width: 100vw;
overflow: hidden;
background-size: cover;
position: relative;
image {
width: 100%;
position: absolute;
bottom: 0;
}
}
</style>