废话不多说,直接上代码。
组件代码:
<template>
<view class="scroll-tabs-container">
<view class="radiusz bg-white pt-[10rpx] z-[999]" :class="{ 'scroll-tabs-sticky': sticky }">
<u-tabs
id="tabScrollTop"
ref="tabScrollTop"
:list="tabs"
:current="active"
@change="handleChangeTab"
:active-color="mainColor"
bg-color="transparent"
:bar-width="90"
font-size="24"
:gutter="26"
name="title"
>
</u-tabs>
</view>
<view class="content">
<view
class="px-[20rpx] pt-[20rpx] pb-[40rpx] w-full bg-white mb-[30rpx] box-border"
v-for="(item, index) in tabs"
:key="item.id"
:class="'tabs' + index"
>
<view v-if="item.is_show == 1">
<view class="text-center"
><text class="pr-[10rpx]">———</text>{{ item.title
}}<text class="pl-[10rpx]">———</text></view
>
<view class="mt-[40rpx]">
<u-parse :html="item.description"></u-parse>
</view>
<view class="mt-[40rpx]"
><apply-btn
:customClass="customClass"
btnText=""
@popTrue="popTrue"
></apply-btn
></view>
</view>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
const { proxy } = getCurrentInstance() as any
import cache from '@/utils/cache'
import { onShow } from '@dcloudio/uni-app'
/**
* @description props参数说明
* @property [number] scrollTop 外部传入的滚动条高度
* @property [array] tabs tabs
* @property [number] current 设置默认的tabIndex
* @property [number] stickyTop 固定定位的高度
* @property [boolean] sticky 是否设置tab为固定定位
* @property [number] itemOffsetTop 自定义滚动相隔间距,到达会切换tab
* @property [object] tabOptions tab的配置
* @property [boolean] scrollTab 滚动时是否切换到指定的tab
* @property [boolean] clickScroll 点击时是否滚动到相应位置
* @property [number ] duration 滚动持续时长
* @property [number] offsetTop 点击滚动到某一模块时的偏离值,每次点击滚动都会减去这个偏离值,这个值的单位是px,
*/
const mainColor = ref(cache.get('btnCon').btn_bgColor)
const customClass = ref('m-auto') //按钮样式
const props = defineProps({
value: {
type: String || Number,
default: null
},
scrollTab: {
type: Boolean,
default: true
},
clickScroll: {
type: Boolean,
default: true
},
scrollTop: {
type: Number || String,
default: null
},
tabs: {
type: Array,
default: () => [] as any[]
},
// 设置默认的tabIndex
current: {
type: Number,
default: 0
},
stickyTop: {
type: String,
default: null
},
// 是否将tab设为固定定位
sticky: {
type: Boolean,
default: true
},
// 自定义间距 ,当 top小于等于这个距离的时候会切换tab
itemOffsetTop: {
type: Number,
default: 60
},
tabOptions: {
type: Object,
default: () => ({})
},
duration: {
type: Number,
default: 300
},
offsetTop: {
type: Number,
default: 0
}
})
const tabTopHeight = ref(0)
const active = ref(0)
const click = ref(false)
const timer = ref()
watch(
[() => props.scrollTop, () => active.value],
([newScrollTop, newActive], [oldScrollTop, oldActive]) => {
if (newScrollTop) {
if (!click.value) {
scrollToTab()
}
}
if (newActive != oldActive) {
nextTick(() => {
scrollToElement()
})
}
},
{ immediate: true }
)
onMounted(() => {
getScrollTabTopHeight()
})
onShow(() => {
isPopup.value = false
})
/**
* @description: 跳转加盟申请页面
* @param {*} isLogin 是否登陆
* @return {*}
*/
const popTrue = (isLogin: boolean) => {
if (!isLogin) {
isPopup.value = !isLogin
} else {
uni.navigateTo({
url: linkUrl.value
})
}
}
const getScrollTabTopHeight = async () => {
// 获取tab的高度
const query = (await createSelectorQueryForThis('#tabScrollTop', false)) as any
if (!query) return
tabTopHeight.value = query.height
}
const createSelectorQueryForThis = (selector: string, all: any) => {
return new Promise((resolve) => {
proxy
.createSelectorQuery()
[all ? 'selectAll' : 'select'](selector)
.boundingClientRect((rect: any) => {
resolve(rect)
})
.exec()
})
}
const createSelectorQuery = (selector: any, all: any) => {
return new Promise((resolve) => {
proxy
.createSelectorQuery()
[all ? 'selectAll' : 'select'](selector)
.boundingClientRect((rect: any) => {
resolve(rect)
})
.exec()
})
}
const emit = defineEmits<{
(event: 'onChange', index: number): void
(event: 'input', active: any): void
}>()
const handleChangeTab = (index: any) => {
active.value = index
click.value = true
}
// 点击滑动到指定元素
const scrollToElement = async () => {
if (!click.value) return false
const tab = props.tabs[active.value]
if (!tab) return
const { scroll_id } = tab as any
if (!scroll_id) return false
clearTimeout(timer.value)
const queryData = (await createSelectorQuery(`.${scroll_id}`, false)) as any
if (tabTopHeight.value === 0) await getScrollTabTopHeight()
if (!queryData) {
click.value = true
return false
}
let scrollTop = props.scrollTop + queryData.top - props.offsetTop
scrollTop -= tabTopHeight.value
// 页面滚动函数
uni.pageScrollTo({
scrollTop,
duration: props.duration,
success: () => {
timer.value = setTimeout(() => {
click.value = false
}, props.duration + 500)
}
})
}
const scrollToTab = async () => {
if (!props.scrollTab && !click.value) return false
const length = props.tabs.length
let allClass = ''
for (let i = 0; i < length; i++) {
const { scroll_id } = props.tabs[i] as any
allClass += i < length - 1 ? `.${scroll_id},` : `.${scroll_id}`
}
const queryData = (await createSelectorQuery(allClass, true)) as any
for (let i = 0; i < queryData.length; i++) {
if (queryData[i].top <= props.itemOffsetTop) {
active.value = i
}
}
}
</script>
<style lang="scss" scoped>
.scroll-tabs-container {
.status_bar {
display: none;
// height: var(--status-bar-height);
}
.scroll-tabs-sticky {
z-index: 9999;
overflow: hidden;
-webkit-transform: translateZ(0);
transform: translate3d(0, 0, 0);
background: white;
}
}
.radiusz {
border-radius: 26rpx 26rpx 0 0;
margin-top: -98rpx;
border-bottom: 2px solid #eee;
position: fixed;
width: 100%;
}
</style>
页面调用代码:
<template>
<view class="bg-[#F3F4F6] w-full">
<scroll-tabs
:value="current"
:tabs="graphicIntroduction"
:tabOptions="{ label: 'title', activeColor: '#222', barColor: '#ff9f0f' }"
:offsetTop="180"
:scrollTop="scrollTop"
:sticky="true"
:itemOffsetTop="300 + statusBarHeight"
:clickScroll="clickScroll"
>
</scroll-tabs>
</view>
</template>
<script lang="ts" setup>
import { getMiniNavigation } from '@/api/shop'
import { onLoad, onPageScroll } from '@dcloudio/uni-app'
const bannerImg = ref('')
const graphicIntroduction = ref([]) as any
const current = ref(0) // tab默认索引
const scrollTop = ref(0)
const clickScroll = ref(false)
const stickyTop = ref()
const statusBarHeight = ref()
onPageScroll((e: any) => {
scrollTop.value = e.scrollTop
})
onLoad(() => {
getMiniNavigationData()
// getHeight()
uni.getSystemInfo({
success: function (e: any) {
stickyTop.value = e.statusBarHeight + 44 + 'px'
statusBarHeight.value = e.statusBarHeight
}
})
})
/**
* @description: 获取首页轮播图和图文介绍
* @return {*}
*/
const getMiniNavigationData = async () => {
const { data } = await getMiniNavigation({ type: 'plan' })
bannerImg.value = data.bannerImg
const list = data.graphicIntroduction
list.forEach((item: any) => {
if (item.is_show == 1) {
graphicIntroduction.value.push(item)
}
})
graphicIntroduction.value.forEach((item: any, index: number) => {
item.scroll_id = `tabs${index}`
})
}
</script>
效果图: