前言
在我们做项目的使用常常会使用条件渲染去有选择的给用户展示相关页面,如果渲染的数据或场景比较多比较复杂,那么往往需要3、4s的时间去完成,用户点击了之后就会陷入3、4s的空白期,并且这段时间屏幕是处于一种”未响应“的状态,用户并不知道自己是否点击生效了。这篇文章主要是介绍一种让条件渲染展示页面更丝滑的方法,这样能大大提高用户的体验。
效果图
这个看上去是在多个页面进行跳转,实则是只有一个页面,不过是用了条件渲染和loading动画让用户感觉自己跳转了页面。这里就不具体介如何使用条件渲染来将多个页面整合成一个页面了,我之前的博客有详细的描述(https://blog.csdn.net/Zchengjisihan/article/details/129016013?spm=1001.2014.3001.5501)。这篇博客我们将重点放在loading动画上。
核心思想
使用外部组件库的vant-weapp的Popup组件
使用CSS的animation动画
使用JS的setTimeout来等全部数据渲染完成后再隐藏loading动画
vant-weapp的Popup组件
我使用的是样式是从上方弹出并且是圆角的形式,你可以根据自己的需求来调整从哪个方向弹出,是否显示 ”× “ 等等,这些Popup组件支持的api你均可以在官方文档下找的到,在这我就不赘述了
在Popup上添加loading样式
这里需要用到相关CSS的animation知识,如果你想换个更炫酷的loading动画可以直接在相关网站上搜索,很多源码的。我把自己的loading动画放在下面了(这个是参考一个博主的,修改了主色调)
<!-- 弹出层 -->
<van-popup show="{{ popShow }}" position="top" custom-style="height: 300rpx;" round>
<!-- loading盒子,用来装loading动画的 -->
<view class="loadingBox" style="margin-top: 200rpx;">
<view class="loader-dots">
<view></view>
<view></view>
<view></view>
<view></view>
<view></view>
</view>
</view>
</van-popup>
.loader-dots view{
width: 40rpx;
height: 40rpx;
background: brown;
border-radius: 30%;
display:inline-block;
animation: slide 1s infinite;
}
.loader-dots view:nth-child(1){
animation-delay:.1s;
}
.loader-dots view:nth-child(2){
animation-delay:.2s;
}
.loader-dots view:nth-child(3){
animation-delay:.3s;
background: linear-gradient(to bottom right,rgb(155, 55, 55),#FFFFFF)
}
.loader-dots view:nth-child(4){
animation-delay:.4s;
background: linear-gradient(to bottom right,rgb(145, 55, 55),#FFFFFF)
}
.loader-dots view:nth-child(5){
animation-delay:.5s;
background:linear-gradient(to bottom right,rgb(135, 55, 55),#FFFFFF)
}
@keyframes slide{
0%{
transform: scale(1)
}
50%
{
opacity:0.3;
transform:scale(2);
}
100%{
transform: scale(1)
}
若渲染数据较大或增加loading的时间需使用延迟函数setTimeout
除了上诉情况,我们也可以在登录页面的跳转的时候添加这个loading动画。
使用setTimeout的好处一方面是可以等待数据全部渲染完成后再展示给用户,从而避免了数据残留以及数据错位的情况;另一方面则是可以自主设置最短loading动画的时间。
例:如上图:虽然渲染完全部的数据只花了几毫秒,但是为了稍微的延迟登录时间,我将setTimeout的时间参数调整到了1000(1s)。我将所有代码放在下面了,需要的自取~
<!-- wxml -->
<!-- 原始内容层 -->
<view class="box">
<van-divider contentPosition="center"
customStyle="color: grey; border-color: grey; font-size: 36rpx; width: 90% ; margin-left:40rpx ; margin-right:40rpx ; margin-top:150rpx"
>
团团活动管理
</van-divider>
<button bindtap="goIndex" class="button_location" style="width: 450rpx; height: 100rpx">
<text style="font-size: 36rpx; float: left; margin-left: 80rpx;">用户端登录</text>
<van-icon name="friends-o" size="60rpx" custom-style="height:60rpx; margin-top:15rpx;"/>
</button>
<button bindtap="goNext" class="Teacherbutton_location" style="width: 450rpx; height: 100rpx">
<text style="font-size: 36rpx; float: left; margin-left: 80rpx;">审批端登录</text>
<van-icon name="manager-o" size="60rpx" custom-style="height:60rpx; margin-top:15rpx;"/>
</button>
<!-- 右下角的图书和提示字 -->
<view style="width: 140rpx;height: 180rpx;position: absolute; right: 40rpx; bottom: 70rpx;" bindtap="lookGuide">
<view style="width: 140rpx;height: 40rpx; font-size: 28rpx; text-align: center; font-weight: 700; color: brown;">手册及日志</view>
<image src="../../icon/guide.png" style="width: 140rpx;height: 140rpx;"></image>
</view>
<van-divider contentPosition="center"
customStyle="color: grey; border-color: grey; font-size: 36rpx; width: 90% ; position: absolute;margin-left:40rpx ; margin-right:40rpx ; bottom:0px"
>
Copyright© STU引力弹簧工作室
</van-divider>
</view>
<!-- 遮罩层 -->
<van-overlay show="{{ show }}" z-index="2">
<view class="wrapper">
<view class="login">
<view class="loginHead">
<image src="../../icon/tuantuan.png" class="tuantuan"></image>
<view class="cross">
<van-icon name="cross" size="40px" custom-style="position:relative; margin-top:20rpx; margin-left:80rpx" bindtap="onClickHide"/>
</view>
</view>
<view class="loginBody">
<van-divider
contentPosition="left"
customStyle="color: grey; border-color: grey; font-size: 36rpx; width: 520rpx ; position:relative; padding-top:80rpx; padding-left:40rpx; ">
Account
</van-divider>
<input bindinput="getAccount" class="inputborder1" placeholder="输入账号"></input>
<van-divider
contentPosition="left"
customStyle="color: grey; border-color: grey; font-size: 36rpx; width: 520rpx ; position:relative; padding-left:40rpx;"
>
Password
</van-divider>
<input class="inputborder2" type="password" placeholder="输入密码" value='{{password}}' bindinput='getPassWord'></input>
<view bindtap="goRegister" class="goRegister">注册账号</view>
<view bindtap="goRetrieve" class="goRetrieve">忘记密码</view>
</view>
<view class="loginFeet">
<view class="loginButton">
<button bindtap="enterIndex" style="width: 88%;border-radius: 40rpx;background-color: #D43030; color:#FFFFFF;box-shadow: 16rpx 8rpx 24rpx rgba(212,48,48, 0.35);">登录</button>
</view>
<image src="../../icon/client-side.png" class="client-side"></image>
</view>
</view>
</view>
<!-- 由于不显示导航栏故loading盒子需要修改margin-top值 -->
<van-popup show="{{ popShow }}" position="top" custom-style="height: 300rpx;" round>
<view class="loadingBox" style="margin-top: 200rpx;">
<view class="loader-dots">
<view></view>
<view></view>
<view></view>
<view></view>
<view></view>
</view>
</view>
</van-popup>
</van-overlay>
const db = wx.cloud.database()
Page({
data: {
popShow:false,
show:false,
password :'',
account :''
},
goNext(e){
console.log('点击了审批端登录')
wx.navigateTo({
url: '../nextChoice/nextChoice',
})
},
goIndex(e){
console.log('点击了用户登录')
//显示用户端的遮罩层
this.setData({
show:true
})
},
// 隐藏遮罩层
onClickHide() {
this.setData({
show: false
});
},
//点击登录
enterIndex(){
let that = this
let account = this.data.account
let password = this.data.password
db.collection("studentUser")
.where({
account:account
})
.get({})
.then(res=>{
console.log("查询数据库成功",res.data)
if(password == res.data[0].password){
console.log('登录成功')
this.setData({
popShow:true
})
setTimeout(() => {
wx.switchTab({
url: '../index/index',
success(){
that.setData({
popShow:false
})
}
})
}, 1000);
}
else{
console.log("登录失败")
wx.showToast({
title: '登录失败,账号或密码不正确',
icon : "none"
})
}
})
.catch(res=>{
wx.showToast({
title: '登录失败,账号或密码不正确',
icon : "none"
})
})
},
//获取输入的账号
getAccount(e){
this.setData({
account : e.detail.value
})
},
//获取输入的密码
getPassWord: function(e) {
var password = e.detail.value;
this.setData({
password: password
})
},
//进入注册界面
goRegister(){
wx.navigateTo({
url: '../closeRegister/closeRegister',
})
},
//进入找回账号密码页面
goRetrieve(){
wx.navigateTo({
url: '../retrieve/retrieve',
})
}
})
{
//json
"usingComponents": {
"van-icon": "@vant/weapp/icon/index",
"van-overlay": "@vant/weapp/overlay/index",
"van-divider": "@vant/weapp/divider/index",
"van-popup": "@vant/weapp/popup/index"
},
"navigationStyle": "custom"
}
当前页面的CSS
text{
padding-right: 10px;
}
.loginHead{
width: 100%;
height: 160rpx;
}
.cross{
float: right;
width: 160rpx;
height: 120rpx;
padding-top: 40rpx;
padding-right: 40rpx;
}
/* 团团图片样式 */
.tuantuan{
width: 160rpx;
height: 120rpx;
padding-top: 40rpx;
padding-left: 40rpx;
}
.loginBody{
width: 100%;
height: 700rpx;
}
.loginFeet{
width: 100%;
height: 300rpx;
}
/* 遮罩层内嵌盒子包装层 */
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
/* 遮罩层内嵌盒子内容层 */
.login{
background-color: #FFFFFF;
width: 600rpx;
height: 1200rpx;
border-radius: 40rpx;
}
/* 输入账号的input */
.inputborder1{
margin-left: 40rpx;
margin-right:40rpx;
margin-bottom: 30rpx;
padding-top: 30rpx;
padding-bottom: 30rpx;
padding-left: 30rpx;
padding-right:30rpx;
border-radius: 20rpx;
border: 2rpx solid #F2E6E6;
box-shadow: 16rpx 8rpx 24rpx rgba(212,48,48, 0.15);
}
/* 输入密码的input */
.inputborder2{
margin-left: 40rpx;
margin-right:40rpx;
margin-bottom: 30rpx;
padding-top: 30rpx;
padding-bottom: 30rpx;
padding-left: 30rpx;
padding-right:30rpx;
border-radius: 20rpx;
border: 2rpx solid #F2E6E6;
box-shadow: 16rpx 8rpx 24rpx rgba(212,48,48, 0.15);
}
/* button在wxss里面修改不了宽度和高度,故在wmxl里面添加style属性来实现 */
.loginButton{
position: relative;
padding-top: 100rpx;
}
.goRegister{
position: relative;
top: 5rpx;
float: right;
right: 40rpx;
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
}
.goRetrieve{
position: relative;
top: 5rpx;
left: 40rpx;
float: left;
font-family: 'Franklin Gothic Medium', 'Arial Narrow', Arial, sans-serif;
}
.client-side{
width: 120rpx;
height: 80rpx;
float: right;
margin-top:10rpx ;
margin-right:20rpx;
}
总页面的CSS
/* choicePage 和 nextChoice的两个按钮 */
.Teacherbutton_location{
margin-top: 20rpx;
border-radius: 80rpx;
color:black;
background-color: #FFFFFF;
box-shadow: 16rpx 8rpx 24rpx rgba(212,48,48, 0.15);
border: 2rpx solid brown;
}
.button_location{
border-radius: 80rpx;
margin-top: 420rpx;
color:#FFFFFF;
background-color: #D43030;
box-shadow: 16rpx 8rpx 24rpx rgba(212,48,48, 0.15);
border: 2rpx solid #FFFFFF;
}
/* 无数据时显示的图片和文字格式 */
.noData{
height: 700rpx;
width: 100%;
position: absolute;
top: 200rpx;
left: 0px;
margin-top: 50rpx;
}
.fail{
height: 700rpx;
width: 100%;
position: absolute;
top: 100rpx;
margin-top: 50rpx;
}
.tip{
position: relative;
margin-top: 800rpx;
font-size: 36rpx;
color:gray;
text-align: center;
}
/* 所有状态标签的样式 */
.state_0{
float: right;
width: 160rpx;
height: 80rpx;
background-color:#FFC300;
border-radius: 0 30rpx 0 30rpx;
}
.state_1{
width: 160rpx;
height: 80rpx;
background-color:#43CF7C;
border-radius: 0 30rpx 0 30rpx;
z-index: 2;
}
.state_2{
width: 160rpx;
height: 80rpx;
background-color:#FF5733;
border-radius: 0 30rpx 0 30rpx;
z-index: 2;
}
.state_4{
float: right;
width:160rpx;
height:80rpx;
background-color:#CCCCCC;
border-radius:0 30rpx 0 30rpx;
z-index: 2;
flex-direction: row;
position: relative;
margin-left: 40rpx;
}
/* 用来装标签的盒子 */
.state_content{
position: relative;
margin-top:20rpx ;
margin-left: 30rpx;
font-size: 30rpx;
color: white;
}
/* 预约老师和申请活动的盒子 */
.mine_application{
margin-left: 50rpx;
margin-right: 50rpx;
}
.mine_application_title{
height: 90rpx;
border-bottom: 5rpx solid #A6A6A6;
font-size: 56rpx;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.mine_application_content{
height: 300rpx;
width: 100%;
display: flex;
position: relative;
box-shadow: 16rpx 8rpx 24rpx rgba(212,48,48, 0.1);
margin-top: 40rpx;
border-radius:30rpx;
background-color: rgb(252, 252, 252);
border: 1rpx solid rgb(210, 210, 210);
}
.mine_appointment_title{
height: 90rpx;
border-bottom: 5rpx solid #A6A6A6;
font-size: 56rpx;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.mine_appointment_content{
height: 500rpx;
width: 100%;
display: flex;
position: relative;
box-shadow: 16rpx 8rpx 24rpx rgba(212,48,48, 0.1);
margin-top: 30rpx;
border-radius: 30rpx;
background-color: rgb(252, 252, 252);
border: 1rpx solid rgb(210, 210, 210);
}
/* 活动的具体样式 */
.event{
font-size: 40rpx;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
position: relative;
margin:30rpx;
width: 90%;
flex: 1;
}
.eventTime{
margin-top: 10rpx;
font-size: 28rpx;
color: #A0A9BD;
}
/* loading盒子的样式 */
.loadingBox{
margin-top: 60rpx;
padding-left: 260rpx;
}
.loader-dots view{
width: 40rpx;
height: 40rpx;
background: brown;
border-radius: 30%;
display:inline-block;
animation: slide 1s infinite;
}
.loader-dots view:nth-child(1){
animation-delay:.1s;
}
.loader-dots view:nth-child(2){
animation-delay:.2s;
}
.loader-dots view:nth-child(3){
animation-delay:.3s;
background: linear-gradient(to bottom right,rgb(155, 55, 55),#FFFFFF)
}
.loader-dots view:nth-child(4){
animation-delay:.4s;
background: linear-gradient(to bottom right,rgb(145, 55, 55),#FFFFFF)
}
.loader-dots view:nth-child(5){
animation-delay:.5s;
background:linear-gradient(to bottom right,rgb(135, 55, 55),#FFFFFF)
}
@keyframes slide{
0%{
transform: scale(1)
}
50%
{
opacity:0.3;
transform:scale(2);
}
100%{
transform: scale(1)
}
}
结语
如果有疑问欢迎大家留言讨论,你如果觉得这篇文章对你有帮助可以给我一个免费的赞吗?我们之间的交流是我最大的动力!