背景
因项目是vue3,小程序简单的封装了一个自定义导航栏,(菜鸟一名有不对的地方勿喷欢迎留言指正)
<template>
<scroll-view
v-bind="$attrs"
:scroll-with-animation="true"
:scroll-y="true"
:upper-threshold="state.upperThreshold"
:class="[classes()]"
:style="scrollStyle"
@scroll="scroll"
@scrolltoupper="scrolltoupper"
@refresherrefresh="refresh"
:refresherTriggered="refresherTriggered"
:scrollTop="scrollY"
>
<image :class="classes('bg--image')" v-if="props.bgImage" style="width: 100%" :src="props.bgImage" />
<view :class="classes('bar')" :style="style">
<slot name="bar">
<view v-if="props.showNavBar" :class="classes('bar--wrp')">
<view :class="classes('bar--icon')" @tap.stop="goBack">
<RectLeft />
</view>
<view :class="classes('bar--title')">{{ props.title }}</view>
</view>
</slot>
</view>
<view :class="classes('body')" :style="bodyStyle">
<slot></slot>
</view>
</scroll-view>
</template>
<script setup>
import { computed, ref, defineEmits, reactive } from 'vue';
import { useSystemInfoStore } from '@/store';
import { useClass, useCreateSelectorQuery } from '@/utils';
import * as Taro from '@tarojs/taro';
import { RectLeft } from '@nutui/icons-vue-taro';//这里需要引入京东的框架
const { system, tabBarHeight, navBarHeight, rectInfo } = useSystemInfoStore();
const { classes } = useClass('mx-nav-bar-container');//这个在公共文件里
const refresherTriggered = ref(false);
const emit = defineEmits(['scroll', 'scrolltoupper']);
const props = defineProps({
scrollY: {
type: Number,
},
refresh: {
//下拉刷新回调方法
type: Function,
defualt: () => {},
},
bgImage: {
//背景图
type: String,
},
isTabBar: {
//是否在tabBar页面
type: Boolean,
},
isNavBarFixed: {
//滑动时NavBar是否fixed布局
type: Boolean,
default: true,
},
navBarColor: {
//navBar背景色
type: String,
},
activeNavBarColor: {
//激活时navBar背景色
type: String,
},
title: {
type: String,
default: '标题',
},
navBarTextColor: {
//navBar字体色
type: String,
default: '#ffffff',
},
activeNavBarTextColor: {
//激活时布局时navBar字体色
type: String,
},
showNavBar: {
//是否需要navBar
type: Boolean,
default: true,
},
goBack: {
//自定义后退方法
type: Function,
default: () => {
const pages = Taro.getCurrentPages();
if (pages.length > 1) {
Taro.navigateBack();
} else {
Taro.switchTab({
url: '/pages/index/index',
});
}
},
},
node: String, //滑动时需要计算的节点id,不穿默认一滑动就激活navBar
bodyExceed: {
//body内容超出
type: Boolean,
default: false,
},
});
const state = reactive({
scrollTop: false,
upperThreshold: 20,
});
const bodyStyle = computed(() => {
return {
minHeight: `${system.screenHeight}px`,
paddingTop: props.bodyExceed ? '0px' : `${navBarHeight}px`,
};
});
const style = computed(() => {
const paddingRight = system.windowWidth - rectInfo.right;
const opts = {};
if (state.scrollTop) {
opts.background = props.activeNavBarColor;
opts.color = props.activeNavBarTextColor;
} else {
opts.background = props.navBarColor;
opts.color = props.navBarTextColor;
}
return {
height: `${navBarHeight}px`,
padding: `${system.statusBarHeight}px ${paddingRight}px 0 ${paddingRight}px`,
...opts,
};
});
const scrollStyle = computed(() => {
const tabHeight = props.isTabBar ? tabBarHeight : 0;
return {
height: `${system.screenHeight - tabHeight}px`,
};
});
const goBack = () => {
props.goBack();
};
const scroll = async e => {
emit('scroll', e);
if (state.scrollStops) {
return;
}
if (props.node) {
const { selectorQueryElemet } = useCreateSelectorQuery(props.node);
const { top } = await selectorQueryElemet();
const scrollTop = top - navBarHeight;
state.scrollTop = scrollTop < 0 ? true : false;
} else {
const { scrollTop } = e.detail;
if (scrollTop <= state.upperThreshold) {
return;
}
state.scrollTop = true;
}
};
const scrolltoupper = e => {
emit('scrolltoupper', e);
if (!props.node) {
state.scrollTop = false;
}
};
const refresh = () => {
refresherTriggered.value = true;
props.refresh(() => {
refresherTriggered.value = false;
});
};
</script>
<style lang="scss">
.mx-nav-bar-container {
box-sizing: border-box;
position: relative;
&__bg--image {
position: absolute;
top: 0;
left: 0;
}
&__bar {
position: fixed;
width: 100%;
padding-left: 16px;
padding-right: 16px;
z-index: 2;
transition: all 0.8s;
&--wrp {
position: relative;
width: 100%;
height: 100%;
}
box-sizing: border-box;
&--title {
display: flex;
justify-content: center;
align-items: center;
font-size: 16px;
font-weight: bold;
height: 100%;
}
&--icon {
position: absolute;
top: 50%;
font-size: 24px;
transform: translateY(-50%);
padding-right: 10px;
}
}
&__body {
position: relative;
box-sizing: border-box;
padding-bottom: calc(constant(safe-area-inset-bottom));
padding-bottom: calc(env(safe-area-inset-bottom));
z-index: 1;
}
}
</style>
使用
<template>
<view class="buyPage">
<NavBarContainer :bodyExceed="true" :title="data.title" activeNavBarColor="#ffffff">
<!-- 页面代码 -->
</NavBarContainer>
</view>
</template>
<script setup>
/*具体路由根据实际路径写*/
import Taro, { useRouter, useLoad, useDidShow, useReady, useUnload } from '@tarojs/taro';
import { reactive } from 'vue';
import NavBarContainer from '@/components/NavBarContainer';
const data = reactive({
title: '标题',
});
useLoad(() => {
console.log('--0-');
});
useDidShow(() => {
console.log('--1-');
});
useReady(() => {
console.log('--2-');
});
useUnload(() => {
console.log('--3-');
});
</script>
公共文件
const { classes } = useClass('mx-nav-bar-container');//这个在公共文件里
/**
*
* @param {*} name
* @returns
*/
export const useClass = (name = '') => {
return {
classes: function (fix) {
return fix ? `${name}__${fix}` : name;
},
};
};
README
API
Props
参数 说明 类型 默认值 refresh 下拉刷新回调方法,通过 Callback
形式处理下拉刷新Function
>()=>{} bgImage 背景图 String
- isTabBar 是否 TabBar
页面Boolean
false isNavBarFixed 滑动时顶部 NavBar
是否浮动布局Boolean
true navBarColor 顶部 NavBar
背景色String
- activeNavBarColor 顶部 NavBar
浮动布局时背景色String
- title 顶部 NavBar
标题String
标题 navBarTextColor 顶部 NavBar
标题颜色String
#ffffff activeNavBarTextColor 顶部 NavBar
标题浮动时颜色String
- showNavBar 是否展示顶部 NavBar
Boolean
true goBack 自定义顶部后退方法 Function
()=>{} node 滑动到某个节点id后改变 NavBar
颜色String
- bodyExceed body内容超出布局 Boolean
false