项目文件下载地址
实际效果如下:
注意事项:
animate.css需要添加样式兼容微信小程序;
微信小程序滚动时boundingClientRect获取不到标签信息
1、HBuilderX打开uniapp创建的vue3项目,在编辑器下方打开终端输入npm install animate.css --save 安装模块
animate.css官网地址
参考官方文档安装使用animate.css
npm install animate.css --save
2、main.js中引入animate.css
import 'animate.css';
3、app.vue中添加全局样式兼容微信小程序
项目中我已将改代码放入base.scss文件中
//animate.css 兼容小程序
page {
--animate-duration: 1s;
--animate-delay: 1s;
--animate-repeat: 1;
}
4、根据animate.css官网文档给标签添加动画,然后再通过js监听屏幕滚动当标签出现在屏幕可视区时给标签切换class,页面全部代码如下:
main-layout 是自定义组件,除去即可
<template>
<view class="home-wrap">
//
<view class="animate-item animate__animated " v-for="(item,index) in domList" :id="'item'+item.id"
:class="item.show?item.showClass:item.hideClass">{{item.text}}</view>
//
</view>
</template>
<script lang="ts">
import {
defineComponent,
computed,
onMounted,
watch,
ref,
getCurrentInstance,
reactive,
nextTick,
toRefs,
toRef
} from 'vue';
export default {
setup(props,context){
//dom节点列表
let domList: {
id: String | Number,
text: String,
show: Boolean,
showClass: String,
hideClass: String,
siteInfo: Object
} [] = ref([{
id: 1,
text: '1',
show: true,
showClass: 'animate__fadeInLeftBig',
hideClass: 'animate__fadeOutLeftBig',
siteInfo: {}
}, {
id: 2,
text: '2',
show: true,
showClass: 'animate__fadeInRightBig',
hideClass: 'animate__fadeOutRightBig',
siteInfo: {}
}, {
id: 3,
text: '3',
show: true,
showClass: 'animate__fadeInLeftBig',
hideClass: 'animate__fadeOutLeftBig',
siteInfo: {}
}, {
id: 4,
text: '4',
show: true,
showClass: 'animate__fadeInRightBig',
hideClass: 'animate__fadeOutRightBig',
siteInfo: {}
}, {
id: 5,
text: '5',
show: true,
showClass: 'animate__fadeInLeftBig',
hideClass: 'animate__fadeOutLeftBig',
siteInfo: {}
}, {
id: 6,
text: '6',
show: true,
showClass: 'animate__fadeInRightBig',
hideClass: 'animate__fadeOutRightBig',
siteInfo: {}
}, {
id: 7,
text: '7',
show: true,
showClass: 'animate__fadeInLeftBig',
hideClass: 'animate__fadeOutLeftBig',
siteInfo: {}
}, {
id: 8,
text: '8',
show: true,
showClass: 'animate__fadeInRightBig',
hideClass: 'animate__fadeOutRightBig',
siteInfo: {}
}, {
id: 9,
text: '9',
show: true,
showClass: 'animate__fadeInLeftBig',
hideClass: 'animate__fadeOutLeftBig',
siteInfo: {}
}])
// 获取上下文this
const instance = getCurrentInstance();
// 获取屏幕高度
const sysInfo = uni.getSystemInfoSync();
// 屏幕滚动防抖定时器
let scrollTimer = null;
const screenScroll = (): void => {
//屏幕滚动停止后获取dom节点信息,执行动画
if (scrollTimer) clearTimeout(scrollTimer);
scrollTimer = setTimeout(() => {
domList.value.forEach(async (t, i) => {
await getNodeInfo('item' + t.id).then(res => {
domList.value[i].siteInfo = res;
})
if (i == domList.value.length - 1) {
loadAni();
}
})
}, 100)
}
const getNodeInfo = (id: String): any => {
// 获取位置信息并返回
return new Promise(resolve => {
const query = uni.createSelectorQuery().in(instance);
query.select('#' + id).boundingClientRect(data => {
// console.log("得到布局位置信息" + JSON.stringify(data));
// console.log("节点离页面顶部的距离为" + data.top);
resolve({
domInfo: data ? data.height : 100,
domTop: data ? data.top : 100
})
}).exec();
});
}
const loadAni = (): void => {
const screenH: String | Number = sysInfo.screenHeight;
domList.value.forEach((t, i) => {
if ((t.siteInfo.domTop > 0) && (t.siteInfo.domTop < screenH)) {
domList.value[i].show = true;
} else {
domList.value[i].show = false;
}
})
}
onMounted(() => {
screenScroll();
})
return{
domList,
screenScroll
}
},
onPageScroll() {
this.screenScroll();
},
onReachBottom() {
console.log('bottom')
}
}
</script>
<style lang="scss">
.home-wrap{
overflow: hidden;
.animate-item{
border: 1px solid #bbb;
margin: 10px 10px 100px;
text-align: center;
padding: 10px;
}
}
</style>
1、pages.json中这只导航栏样式为自定义
"globalStyle": {
"navigationStyle": "custom"
},
2、编写头部组件代码
<template>
<view class="main-wrap" :style="'padding-top:'+(statusBarHeight+titleBarHeight)+'px;'">
<view class="head-bar">
//状态栏占位
<view class="status-bar" :style="'height:'+statusBarHeight+'px'">
</view>
<view class="title-bar" :style="'height:'+titleBarHeight+'px'">
//导航左侧返回键
<view class="title-bar-left">
<uni-icons type="back" size="30" v-if="back" @click="backPage"></uni-icons>
</view>
//导航栏中间标题
<view class="title-bar-center">
{{cTitle}}
</view>
//导航栏右侧插槽
<view class="title-bar-right">
<slot name="titleRight"></slot>
</view>
</view>
</view>
//导航栏下方页面内容区域插槽
<view class="main">
<slot></slot>
</view>
</view>
</template>
<script lang="ts">
import { defineComponent, computed, onMounted, watch,ref,getCurrentInstance,reactive,nextTick ,toRefs} from 'vue'
export default {
name:"MainLayout",
props:{
title:{
type:String||Number,
default:''
},
back:{
type:Boolean,
default:false
}
},
created() {
this.setHeaderHeight();
},
setup(props, context){
let statusBarHeight = ref<Number>(0);
let titleBarHeight = ref<Number>(0);
let tabBarHeight = ref<Number>(60);
const setHeaderHeight =():void=>{
uni.getSystemInfo({
success:e=>{
let statusBar = 0 //状态栏高度
let customBar = 0 // 状态栏高度 + 导航栏高度
let navbar = 0 // 自定义标题与胶囊对齐高度
// #ifdef MP
statusBar = e.statusBarHeight
customBar = e.statusBarHeight + 45
if (e.platform === 'android') {
customBar = e.statusBarHeight + 50
}
// #endif
// #ifdef MP-WEIXIN
statusBar = e.statusBarHeight
const custom = wx.getMenuButtonBoundingClientRect()
customBar = custom.bottom + custom.top - e.statusBarHeight
navbar = (custom.top - e.statusBarHeight) * 2 + custom.height
// #endif
// #ifdef MP-ALIPAY
statusBar = e.statusBarHeight
customBar = e.statusBarHeight + e.titleBarHeight
// #endif
// #ifdef APP-PLUS
console.log('app-plus', e)
statusBar = e.statusBarHeight
customBar = e.statusBarHeight + 45
// #endif
// #ifdef H5
statusBar = 0
customBar = e.statusBarHeight + 45
// #endif
titleBarHeight.value = navbar||customBar;
statusBarHeight.value = statusBar;
}
})
}
const cTitle = computed({
get:()=>{
return props.title ;
},
set:()=>{}
})
const backPage = ():void=>{
uni.navigateBack({
//关闭当前页面,返回上一页面或多级页面。
delta:1
});
}
return {
setHeaderHeight,
statusBarHeight,
titleBarHeight,
tabBarHeight,
cTitle,
backPage
}
}
}
</script>
<style lang="scss">
//@import "@/styles/base.scss";
.main-wrap {
display: block;
position: relative;
width:100%;
.head-bar{
position: fixed;
top: 0;
left: 0;
right: 0;
z-index:999;
.status-bar{
background-color: #fff;
}
.title-bar{
display: flex;
align-items: center;
justify-content: space-between;
background-color: $uni-bg-color-nav;
.title-bar-left,.title-bar-right{
width: 50px;
}
.title-bar-center{
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: calc(100% - 100px);
}
}
}
}
</style>
3、 main.js全局引用该组件
import MainLayout from "./layout/index.vue"
//vue2
Vue.component("main-layout", MainLayout);
//vue3
app.component("main-layout", MainLayout);
4、页面上直接使用该组件
<main-layout title="首页" back>
<template v-slot:titleRight>
<text>右插槽</text>
</template>
<text>页面内容</text>
</main-layout>
结语:有用点赞,无用留言批评